master a549376da3ff cached
156 files
640.2 KB
359.4k tokens
1 requests
Download .txt
Showing preview only (684K chars total). Download the full file or copy to clipboard to get everything.
Repository: 0voice/interview_internal_reference
Branch: master
Commit: a549376da3ff
Files: 156
Total size: 640.2 KB

Directory structure:
gitextract_6ovixev9/

├── 01.阿里篇/
│   ├── .gitkeep
│   ├── 1.1.1 如何实现一个高效的单向链表逆序输出?.md
│   ├── 1.1.2 已知sqrt(2)约等于1.414,要求不用数学库,求sqrt(2)精确到小数点后10位.md
│   ├── 1.1.3 给定一个二叉搜索树(BST),找到树中第 K 小的节点.md
│   ├── 1.1.4 LRU缓存机制.md
│   ├── 1.1.5 关于epoll和select的区别,以下哪些说法是正确的.md
│   ├── 1.1.6 从innodb的索引结构分析,为什么索引的 key 长度不能太长.md
│   ├── 1.1.7 MySQL的数据如何恢复到任意时间点?.md
│   ├── 1.1.8 NFS与SMB的区别?.md
│   ├── 1.1.9 输入 ping IP 后敲回车,发包前会发生什么?.md
│   ├── 1.2.0 请解释下为什么鹿晗发布恋情的时候,微博系统会崩溃,如何解决?.md
│   ├── 1.2.1 现有一批邮件需要发送给订阅顾客,且有一个集群(集群的节点数不定,会动态扩容缩容)来负责具体的邮件发送任务,如何让系统尽快地完成发送?.md
│   ├── 1.2.2 有一批气象观测站,现需要获取这些站点的观测数据,并存储到 Hive 中。但是气象局只提供了 api 查询,每次只能查询单个观测点。那么如果能够方便快速地获取到所有的观测点的数据?.md
│   ├── 1.2.3 如何实现两金额数据相加(最多小数点两位).md
│   ├── 1.2.4 关于并行计算的一些基础开放问题.md
│   ├── 1.2.5 请计算XILINX公司VU9P芯片的算力相当于多少TOPS,给出计算过程与公式.md
│   ├── 1.2.6 一颗现代处理器,每秒大概可以执行多少条简单的MOV指令,有哪些主要的影响因素.md
│   ├── 1.2.7 请分析 MaxCompute 产品与分布式技术的关系、当前大数据计算平台类产品的市场现状和发展趋势.md
│   ├── 1.2.8 对大数据平台中的元数据管理是怎么理解的,元数据收集管理体系是怎么样的,会对大数据应用有什么样的影响.md
│   ├── 1.2.9 你理解常见如阿里,和友商大数据平台的技术体系差异以及发展趋势和技术瓶颈,在存储和计算两个方面进行概述.md
│   ├── 1.3.0 在云计算大数据处理场景中,每天运行着成千上万的任务,每个任务都要进行 IO 读写.md
│   ├── 1.3.1 最大频率栈.md
│   ├── 1.3.2 给定一个链表,删除链表的倒数第N个节点,并且返回链表的头结点.md
│   ├── 1.3.3 如果让你设计一个通用的、支持各种数据库秒级备份和恢复的系统,你会如何设计.md
│   ├── 1.3.4 如果让你来设计一个支持数据库、NOSQL 和大数据之间数据实时流动的数据流及处理的系统,你会考虑哪些问题?如何设计?.md
│   ├── 1.3.5 给定一个整数数组和一个整数,返回两个数组的索引,这两个索引指向的数字的加和等于指定的整数。需要最优的算法,分析算法的空间和时间复杂度.md
│   ├── 1.3.6 假如给你一个新产品,你将从哪些方面来保障它的质量?.md
│   ├── 1.3.7 请评估一下程序的执行结果?.md
│   ├── 1.3.8 如何测试一辆自行车.md
│   └── 1.3.9 如何判断两个链表是否相交.md
├── 02.华为篇/
│   ├── .gitkeep
│   ├── 2.1.1 static有什么用途?(请至少说明两种).md
│   ├── 2.1.2 引用与指针有什么区别?.md
│   ├── 2.1.3 描述实时系统的基本特性.md
│   ├── 2.1.4 全局变量和局部变量在内存中是否有区别?如果有,是什么区别?.md
│   ├── 2.1.5 什么是平衡二叉树?.md
│   ├── 2.1.6 堆栈溢出一般是由什么原因导致的?.md
│   ├── 2.1.7 什么函数不能声明为虚函数?.md
│   ├── 2.1.8 冒泡排序算法的时间复杂度是什么?.md
│   ├── 2.1.9. Internet采用哪种网络协议?该协议的主要层次结构?.md
│   ├── 2.2.0 IP地址的编码分为哪俩部分?.md
│   ├── 2.2.1 用户输入M,N值,从1至N开始顺序循环数数,每数到M输出该数值,直至全部输出。写出C程序.md
│   └── 2.2.2 某32位系统下, C++程序,请计算sizeof 的值.md
├── 03.百度篇/
│   ├── .gitkeep
│   ├── 3.4.8 找出数组中出现次数超过一半的数,现在有一个数组,已知一个数出现的次数超过了一半,请用O(n)的复杂度的算法找出这个数。.md
│   └── 3.4.9 找出被修改过的数字.md
├── 04.腾讯篇/
│   └── .gitkeep
├── 05.美团篇/
│   ├── .gitkeep
│   └── 5.2.0 浅复制和深复制?怎样实现深复制?.md
├── 06.头条篇/
│   ├── .gitkeep
│   └── 一棵二叉树,求最大通路长度(即最大左右子树高度之和).md
├── 07.滴滴篇/
│   └── .gitkeep
├── 08.京东篇/
│   ├── .gitkeep
│   └── 简单说一下hadoop和spark的shuffle过程.md
├── 09.MySQL篇/
│   ├── .gitkeep
│   ├── 9.1.0 主键 超键 候选键 外键.md
│   ├── 9.1.1 数据库事务的四个特性及含义.md
│   ├── 9.1.2 视图的作用,视图可以更改么?.md
│   ├── 9.1.3 drop,delete与truncate的区别.md
│   ├── 9.1.4 索引的工作原理及其种类.md
│   ├── 9.1.5 连接的种类.md
│   ├── 9.1.6 数据库范式.md
│   ├── 9.1.7 数据库优化的思路.md
│   ├── 9.1.8 存储过程与触发器的区别.md
│   └── 9.1.9 解释 SQL 的 left join 和 right join.md
├── 10.Redis篇/
│   ├── .gitkeep
│   ├── 10.1.0 使用Redis有哪些好处?.md
│   ├── 10.1.1 redis相比memcached有哪些优势?.md
│   ├── 10.1.2 redis常见性能问题和解决方案.md
│   ├── 10.1.3 MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据.md
│   ├── 10.1.3 zookeeper的四种类型的znode.md
│   ├── 10.1.4 Memcache与Redis的区别都有哪些?.md
│   ├── 10.1.5 Redis 常见的性能问题都有哪些?如何解决?.md
│   ├── 10.1.6 redis最适合的场景.md
│   ├── 10.1.7 Redis的同步机制了解么?.md
│   └── 10.1.8 是否使用过Redis集群,集群的原理是什么?.md
├── 11.MongoDB篇/
│   ├── .gitkeep
│   ├── 11.1.0 什么是MongoDB?.md
│   ├── 11.1.1 MongoDB是由哪种语言写的?.md
│   ├── 11.1.2 MongoDB的优势有哪些?.md
│   ├── 11.1.3 什么是数据库?.md
│   ├── 11.1.4 什么是集合?.md
│   ├── 11.1.5 什么是文档?.md
│   ├── 11.1.6 MongoDB和关系型数据库术语对比图.md
│   ├── 11.1.7 什么是“mongod”?.md
│   ├── 11.1.8 “mongod”参数有什么?.md
│   ├── 11.1.9 什么是“mongo”?.md
│   ├── 11.2.0 MongoDB哪个命令可以切换数据库?.md
│   ├── 11.2.1 什么是非关系型数据库?.md
│   ├── 11.2.2 非关系型数据库有哪些类型?.md
│   ├── 11.2.3 为什么用MOngoDB?.md
│   ├── 11.2.4 在哪些场景使用MongoDB?.md
│   ├── 11.2.5 MongoDB中的命名空间是什么意思.md
│   ├── 11.2.6 哪些语言支持MongoDB.md
│   ├── 11.2.7 在MongoDB中如何创建一个新的数据库?.md
│   ├── 11.2.8 在MongoDB中如何查看数据库列表?.md
│   ├── 11.2.9 MongoDB中的分片是什么意思?.md
│   ├── 11.3.0 如何查看使用MongoDB的连接?.md
│   ├── 11.3.1 什么是复制?.md
│   ├── 11.3.2 在MongoDB中如何在集合中插入一个文档?.md
│   ├── 11.3.3 在MongoDB中如何除去一个数据库?.md
│   ├── 11.3.4 在MongoDB中如何创建一个集合?.md
│   ├── 11.3.5 在MongoDB中如何查看一个已经创建的集合?.md
│   ├── 11.3.6 在MongoDB中如何删除一个集合?.md
│   ├── 11.3.7 为什么要在MongoDB中使用分析器?.md
│   ├── 11.3.8 MongoDB支持主键外键关系吗?.md
│   ├── 11.3.9 MongoDB支持哪些数据类型?.md
│   ├── 11.4.0 为什么要在MongoDB中用Code数据类型?.md
│   ├── 11.4.1 为什么要在MongoDB中用Regular Expression数据类型?.md
│   ├── 11.4.2 为什么在MongoDB中使用Object ID数据类型?.md
│   ├── 11.4.3 如何在集合中插入一个文档?.md
│   ├── 11.4.4 “ObjectID”有哪些部分组成?.md
│   ├── 11.4.5 在MongoDb中什么是索引?.md
│   ├── 11.4.6 如何添加索引?.md
│   ├── 11.4.7 MongoDB有哪些可替代产品?.md
│   ├── 11.4.8 如何查询集合中的文档?.md
│   ├── 11.4.9 用什么方法可以格式化输出结果?.md
│   ├── 11.5.0 如何使用AND或OR条件循环查询集合中的文档?.md
│   ├── 11.5.1 在MongoDB中如何更新数据?.md
│   ├── 11.5.2 如何删除文档?.md
│   ├── 11.5.3 在MongoDB中如何排序?.md
│   ├── 11.5.4 什么是聚合?.md
│   ├── 11.5.5 在MongoDB中什么是副本集?.md
│   └── 11.5.6 Mongodb存储特性与内部原理.md
├── 12.Zookeeper篇/
│   ├── .gitkeep
│   ├── 12.1.0 zookeeper是什么?.md
│   ├── 12.1.1 zookeeper提供了什么?.md
│   ├── 12.1.2 zookeeper文件系统.md
│   ├── 12.1.3 zookeeper的四种类型的znode.md
│   ├── 12.1.4 zookeeper通知机制.md
│   ├── 12.1.5 zookeeper有哪些应用场景?.md
│   ├── 12.1.6 zk的命名服务.md
│   ├── 12.1.7 zk的配置管理服务.md
│   ├── 12.1.8 zk的集群管理.md
│   ├── 12.1.9 zk的分布式锁.md
│   ├── 12.2.0 zk队列管理.md
│   ├── 12.2.1 zk数据复制.md
│   ├── 12.2.2 zk中zab的工作原理.md
│   ├── 12.2.3 zk是如何保证事务的顺序一致性.md
│   ├── 12.2.4 zk集群下server工作状态.md
│   ├── 12.2.5 zk是如何选举Leader的?.md
│   ├── 12.2.6 zk同步流程.md
│   ├── 12.2.7 分布式通知和协调.md
│   └── 12.2.8 zk的session机制.md
├── 13.Nginx篇/
│   └── .gitkeep
├── 14.算法篇/
│   └── .gitkeep
├── 15.内存篇/
│   └── .gitkeep
├── 16.CPU篇/
│   └── .gitkeep
├── 17.磁盘篇/
│   └── .gitkeep
├── 18.网络通信篇/
│   └── .gitkeep
├── 19.安全篇/
│   └── .gitkeep
├── 20.并发篇/
│   └── .gitkeep
├── 2023adding.md
├── 21.面经/
│   └── 2020秋招面经总结.md
├── README.md
└── sync_link

================================================
FILE CONTENTS
================================================

================================================
FILE: 01.阿里篇/.gitkeep
================================================


================================================
FILE: 01.阿里篇/1.1.1 如何实现一个高效的单向链表逆序输出?.md
================================================
##### **问题**:如何实现一个高效的单向链表逆序输出? 

##### **出题人**:阿里巴巴出题专家:昀龙/阿里云弹性人工智能负责人

##### **参考答案**:下面是其中一种写法,也可以有不同的写法,比如递归等。供参考。


```
typedef struct node{
    int           data;
    struct node*  next;
    node(int d):data(d), next(NULL){}
}node;

void reverse(node* head)
{
    if(head == NULL){
        return;
    }

    node* pleft = NULL;
    node* pcurrent = head;
    node* pright = head->next;

    while(pright){
        pcurrent->next = pleft;
        node *ptemp = pright->next;
        pright->next = pcurrent;
        pleft = pcurrent;
        pcurrent = pright;
        pright = ptemp;
    }

    while(pcurrent != NULL){
        cout<< pcurrent->data << "\t";
        pcurrent = pcurrent->next;
    }
}

```

``` java
class Solution<T> {

    public void reverse(ListNode<T> head) {
       if (head == null || head.next == null) {
    	   return ;
       }
       ListNode<T> currentNode = head;
       Stack<ListNode<T>> stack = new Stack<>();
       while (currentNode != null) {
    	   stack.push(currentNode);
    	   ListNode<T> tempNode = currentNode.next;
    	   currentNode.next = null; // 断开连接
    	   currentNode = tempNode;
       }
       
       head = stack.pop();
       currentNode = head;
       
       while (!stack.isEmpty()) {
    	   currentNode.next = stack.pop();
    	   currentNode = currentNode.next;
       }
    }
}

class ListNode<T>{
	T val;
	public ListNode(T val) {
		this.val = val;
	}
	ListNode<T> next;
}
```


================================================
FILE: 01.阿里篇/1.1.2 已知sqrt(2)约等于1.414,要求不用数学库,求sqrt(2)精确到小数点后10位.md
================================================
#### **题目**:已知 sqrt (2)约等于 1.414,要求不用数学库,求 sqrt (2)精确到小数点后 10 位。
#### **出题人**:——阿里巴巴出题专家:文景/阿里云 CDN 资深技术专家
#### **参考答案**:
#### * 考察点

1. 基础算法的灵活应用能力(二分法学过数据结构的同学都知道,但不一定往这个方向考虑;如果学过数值计算的同学,应该还要能想到牛顿迭代法并解释清楚)
2. 退出条件设计

#### 二分法
##### 1. 已知 sqrt(2)约等于 1.414,那么就可以在(1.4, 1.5)区间做二分
查找,如:
a) high=>1.5
b) low=>1.4
c) mid => (high+low)/2=1.45
d) 1.45*1.45>2 ? high=>1.45 : low => 1.45
e) 循环到 c)

##### 2. 退出条件
a) 前后两次的差值的绝对值<=0.0000000001, 则可退出

```
const double EPSILON = 0.0000000001;

double sqrt2() {
    double low = 1.4, high = 1.5;
    double mid = (low + high) / 2;

    while (high - low > EPSILON) {
        if (mid * mid > 2) {
            high = mid;
        } else {
            low = mid;
        }
        mid = (high + low) / 2;
    }

    return mid;
}
```

#### 牛顿迭代法
##### 1.牛顿迭代法的公式为:
x<sub>n+1</sub> = x<sub>n</sub>-f(x<sub>n</sub>)/f'(x<sub>n</sub>)

对于本题,需要求解的问题为:f(x)=x<sup>2</sup>-2 的零点
```
EPSILON = 0.1 ** 10
def newton(x):
    if abs(x ** 2 - 2) > EPSILON:
        return newton(x - (x ** 2 - 2) / (2 * x))
    else:
        return x
```

================================================
FILE: 01.阿里篇/1.1.3 给定一个二叉搜索树(BST),找到树中第 K 小的节点.md
================================================
#### **题目**:给定一个二叉搜索树(BST),找到树中第 K 小的节点。
#### **出题人**:阿里巴巴出题专家:文景/阿里云 CDN 资深技术专家
#### **参考答案**:

##### * 考察点
1. 基础数据结构的理解和编码能力
2. 递归使用

##### * 示例
```
       5
      / \
     3   6
    / \
   2   4
  /
 1
 
 ```
说明:保证输入的 K 满足 1<=K<=(节点数目)

解法1:树相关的题目,第一眼就想到递归求解,左右子树分别遍历。联想到二叉搜索树的性质,root 大于左子树,小于右子树,如果左子树的节点数目等于 K-1,那么 root 就是结果,否则如果左子树节点数目小于 K-1,那么结果必然在右子树,否则就在左子树。因此在搜索的时候同时返回节点数目,跟 K 做对比,就能得出结果了。

```
/**
 * Definition for a binary tree node.
 **/

public class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int x) { val = x; }
}

class Solution {
    private class ResultType {
    
        boolean found;  // 是否找到
        
        int val;  // 节点数目
        ResultType(boolean found, int val) {
            this.found = found;
            this.val = val;
        }
    }

    public int kthSmallest(TreeNode root, int k) {
        return kthSmallestHelper(root, k).val;
    }

    private ResultType kthSmallestHelper(TreeNode root, int k) {
        if (root == null) {
            return new ResultType(false, 0);
        }

        ResultType left = kthSmallestHelper(root.left, k);

        // 左子树找到,直接返回
        if (left.found) {
            return new ResultType(true, left.val);
        }

        // 左子树的节点数目 = K-1,结果为 root 的值
        if (k - left.val == 1) {
            return new ResultType(true, root.val);
        }

        // 右子树寻找
        ResultType right = kthSmallestHelper(root.right, k - left.val - 1);
        if (right.found) {
            return new ResultType(true, right.val);
        }

        // 没找到,返回节点总数
        return new ResultType(false, left.val + 1 + right.val);
    }
}

```

解法2:基于二叉搜索树的特性,在中序遍历的结果中,第k个元素就是本题的解。
最差的情况是k节点是bst的最右叶子节点,不过`每个节点的遍历次数最多是1次`。
遍历并不是需要全部做完,使用计数的方式,找到第k个元素就可以退出。
下面是go的一个简单实现。

```
// BST is binary search tree
type BST struct {
	key, value  int
	left, right *BST
}

func (bst *BST) setLeft(b *BST) {
	bst.left = b
}

func (bst *BST) setRight(b *BST) {
	bst.right = b
}

// count 查找bst第k个节点的值,未找到就返回0
func count(bst *BST, k int) int {
	if k < 1 {
		return 0
	}

	c := 0
	ok, value := countRecursive(bst, &c, k)

	if ok {
		return value
	}

	return 0
}

// countRecurisive 对bst使用中序遍历
// 用计数方式控制退出遍历,参数c就是已遍历节点数
func countRecursive(bst *BST, c *int, k int) (bool, int) {
	if bst.left != nil {
		ok, value := countRecursive(bst.left, c, k)
		if ok {
			return ok, value
		}
	}

	if *c == k-1 {
		return true, bst.value
	}

	*c++

	if bst.right != nil {
		ok, value := countRecursive(bst.right, c, k)
		if ok {
			return ok, value
		}
	}

	return false, 0
}

// 下面是测试代码,覆盖了退化的情况和普通bst

func createBST1() *BST {
	b1 := &BST{key: 1, value: 10}
	b2 := &BST{key: 2, value: 20}
	b3 := &BST{key: 3, value: 30}
	b4 := &BST{key: 4, value: 40}
	b5 := &BST{key: 5, value: 50}
	b6 := &BST{key: 6, value: 60}
	b7 := &BST{key: 7, value: 70}
	b8 := &BST{key: 8, value: 80}
	b9 := &BST{key: 9, value: 90}

	b9.setLeft(b8)
	b8.setLeft(b7)
	b7.setLeft(b6)
	b6.setLeft(b5)
	b5.setLeft(b4)
	b4.setLeft(b3)
	b3.setLeft(b2)
	b2.setLeft(b1)

	return b9
}

func createBST2() *BST {
	b1 := &BST{key: 1, value: 10}
	b2 := &BST{key: 2, value: 20}
	b3 := &BST{key: 3, value: 30}
	b4 := &BST{key: 4, value: 40}
	b5 := &BST{key: 5, value: 50}
	b6 := &BST{key: 6, value: 60}
	b7 := &BST{key: 7, value: 70}
	b8 := &BST{key: 8, value: 80}
	b9 := &BST{key: 9, value: 90}

	b1.setRight(b2)
	b2.setRight(b3)
	b3.setRight(b4)
	b4.setRight(b5)
	b5.setRight(b6)
	b6.setRight(b7)
	b7.setRight(b8)
	b8.setRight(b9)

	return b1
}

func createBST3() *BST {
	b1 := &BST{key: 1, value: 10}
	b2 := &BST{key: 2, value: 20}
	b3 := &BST{key: 3, value: 30}
	b4 := &BST{key: 4, value: 40}
	b5 := &BST{key: 5, value: 50}
	b6 := &BST{key: 6, value: 60}
	b7 := &BST{key: 7, value: 70}
	b8 := &BST{key: 8, value: 80}
	b9 := &BST{key: 9, value: 90}

	b5.setLeft(b3)
	b5.setRight(b7)
	b3.setLeft(b2)
	b3.setRight(b4)
	b2.setLeft(b1)
	b7.setLeft(b6)
	b7.setRight(b8)
	b8.setRight(b9)

	return b5
}

func createBST4() *BST {
	b := &BST{key: 1, value: 10}
	last := b

	for i := 2; i < 100000; i++ {
		n := &BST{key: i, value: i * 10}
		last.setRight(n)

		last = n
	}

	return b
}

func createBST5() *BST {
	b := &BST{key: 99999, value: 999990}
	last := b

	for i := 99998; i > 0; i-- {
		n := &BST{key: i, value: i * 10}
		last.setLeft(n)

		last = n
	}

	return b
}

func createBST6() *BST {
	b := &BST{key: 50000, value: 500000}
	last := b

	for i := 49999; i > 0; i-- {
		n := &BST{key: i, value: i * 10}
		last.setLeft(n)

		last = n
	}

	last = b

	for i := 50001; i < 100000; i++ {
		n := &BST{key: i, value: i * 10}
		last.setRight(n)

		last = n
	}

	return b
}

func TestK(t *testing.T) {
	bst1 := createBST1()
	bst2 := createBST2()
	bst3 := createBST3()
	bst4 := createBST4()

	check(t, bst1, 1, 10)
	check(t, bst1, 2, 20)
	check(t, bst1, 3, 30)
	check(t, bst1, 4, 40)
	check(t, bst1, 5, 50)
	check(t, bst1, 6, 60)
	check(t, bst1, 7, 70)
	check(t, bst1, 8, 80)
	check(t, bst1, 9, 90)

	check(t, bst2, 1, 10)
	check(t, bst2, 2, 20)
	check(t, bst2, 3, 30)
	check(t, bst2, 4, 40)
	check(t, bst2, 5, 50)
	check(t, bst2, 6, 60)
	check(t, bst2, 7, 70)
	check(t, bst2, 8, 80)
	check(t, bst2, 9, 90)

	check(t, bst3, 1, 10)
	check(t, bst3, 2, 20)
	check(t, bst3, 3, 30)
	check(t, bst3, 4, 40)
	check(t, bst3, 5, 50)
	check(t, bst3, 6, 60)
	check(t, bst3, 7, 70)
	check(t, bst3, 8, 80)
	check(t, bst3, 9, 90)

	check(t, bst4, 1, 10)
	check(t, bst4, 2, 20)
	check(t, bst4, 3, 30)
	check(t, bst4, 4, 40)
	check(t, bst4, 5, 50)
	check(t, bst4, 6, 60)
	check(t, bst4, 7, 70)
	check(t, bst4, 8, 80)
	check(t, bst4, 9, 90)

	check(t, bst4, 99991, 999910)
	check(t, bst4, 99992, 999920)
	check(t, bst4, 99993, 999930)
	check(t, bst4, 99994, 999940)
	check(t, bst4, 99995, 999950)
	check(t, bst4, 99996, 999960)
	check(t, bst4, 99997, 999970)
	check(t, bst4, 99998, 999980)
	check(t, bst4, 99999, 999990)
}

func check(t *testing.T, b *BST, k, value int) {
	t.Helper()

	checkCall(t, b, k, value, count)
	// 此处可添加其他解法的实现
}

func checkCall(t *testing.T, b *BST, k, value int, find func(bst *BST, kth int) int) {
	t.Helper()

	got := find(b, k)
	if got != value {
		t.Fatalf("want:%d, got:%d", value, got)
	}
}
```


================================================
FILE: 01.阿里篇/1.1.4 LRU缓存机制.md
================================================
**题目**:LRU 缓存机制
设计和实现一个 LRU(最近最少使用)缓存数据结构,使它应该支持一下操作:get 和 put。
get(key) - 如果 key 存在于缓存中,则获取 key 的 value(总是正数),否则返回 -1。
put(key,value) - 如果 key 不存在,请设置或插入 value。当缓存达到其容量时,它应该在插入新项目之前使最近最少使用的项目作废。

**出题人**:文景/阿里云 CDN 资深技术专家

**参考答案**:

python版本的:

```
class LRUCache(object):
    def __init__(self, capacity):
    """
    :type capacity: int
    """
    self.cache = {}
    self.keys = []
    self.capacity = capacity
    
    def visit_key(self, key):
        if key in self.keys:
            self.keys.remove(key)
        self.keys.append(key)
    
    def elim_key(self):
        key = self.keys[0]
        self.keys = self.keys[1:]
        del self.cache[key]
        
    def get(self, key):
        """
        :type key: int
        :rtype: int
        """
        if not key in self.cache:
            return -1
        self.visit_key(key)
        return self.cache[key]
    
    def put(self, key, value):
        """
        :type key: int
        :type value: int
        :rtype: void
        """
        if not key in self.cache:
        if len(self.keys) == self.capacity:
        self.elim_key()
        self.cache[key] = value
        self.visit_key(key)

def main():
    s =
    [["put","put","get","put","get","put","get","get","get"],[[1,1],[2,2],[1],[3,3],[2],[
    4,4],[1],[3],[4]]]
    obj = LRUCache(2)
    l=[]
    for i,c in enumerate(s[0]):
        if(c == "get"):
            l.append(obj.get(s[1][i][0]))
        else:
            obj.put(s[1][i][0], s[1][i][1])
    print(l)

if __name__ == "__main__":
    main()

```

c++版本的:

```
class LRUCache{
    public:
        LRUCache(int capacity) {
            cap = capacity;
        }
        
        int get(int key) {
            auto it = m.find(key);
            if (it == m.end()) return -1;
            l.splice(l.begin(), l, it->second);
            return it->second->second;
        }
        
        void set(int key, int value) {
            auto it = m.find(key);
            if (it != m.end()) l.erase(it->second);
            l.push_front(make_pair(key, value));
            m[key] = l.begin();
            if (m.size() > cap) {
                int k = l.rbegin()->first;
                l.pop_back();
                m.erase(k);
            }
        }
}

```


================================================
FILE: 01.阿里篇/1.1.5 关于epoll和select的区别,以下哪些说法是正确的.md
================================================
##### **问题**:关于 epoll 和 select 的区别,哪些说法是正确的?(多选)
A. epoll 和 select 都是 I/O 多路复用的技术,都可以实现同时监听多个 I/O 事件的状态。

B. epoll 相比 select 效率更高,主要是基于其操作系统支持的I/O事件通知机制,而 select 是基于轮询机制。

C. epoll 支持水平触发和边沿触发两种模式。

D. select 能并行支持 I/O 比较小,且无法修改。

##### **出题人**:阿里巴巴出题专家:寈峰/阿里技术专家

##### **参考答案**:A,B,C


**【延伸】那在高并发的访问下,epoll使用那一种触发方式要高效些?当使用边缘触发的时候要注意些什么东西?**




================================================
FILE: 01.阿里篇/1.1.6 从innodb的索引结构分析,为什么索引的 key 长度不能太长.md
================================================
#### **题目**:从 innodb 的索引结构分析,为什么索引的 key 长度不能太长?

#### **出题人**:阿里巴巴出题专家:近秋/阿里云数据库产品技术部技术专家

#### **参考答案**:key 太长会导致一个页当中能够存放的 key 的数目变少,间接导致索引树的页数目变多,索引层次增加,从而影响整体查询变更的效率。


================================================
FILE: 01.阿里篇/1.1.7 MySQL的数据如何恢复到任意时间点?.md
================================================
#### **题目**:MySQL 的数据如何恢复到任意时间点?

#### **出题人**:阿里巴巴出题专家:近秋/阿里云数据库产品技术部技术专家参考答案

#### **参考答案**:恢复到任意时间点以定时的做全量备份,以及备份增量的 binlog 日志为前提。恢复到任意时间点首先将全量备份恢复之后,再此基础上回放增加的 binlog 直至指定的时间点。


================================================
FILE: 01.阿里篇/1.1.8 NFS与SMB的区别?.md
================================================
#### **题目**:NFS 和 SMB 是最常见的两种 NAS(Network Attached Storage)协议,当把一个文件系统同时通过 NFS 和 SMB 协议共享给多个主机访问时,以下哪些说法是错误的:(多选)

A. 不可能有这样的操作,即把一个文件系统同时通过 NFS 和 SMB协议共享给多个主机访问。

B. 主机 a 的用户通过NFS 协议创建的文件或者目录,另一个主机 b的用户不能通过 SMB 协议将其删除。

C. 在同一个目录下,主机 a 通过 NFS 协议看到文件 file.txt,主机b 通过 SMB 协议也看到文件 file.txt,那么它们是同一个文件。

D. 主机 a 通过 NFS 协议,以及主机 b 通过 SMB 协议,都可以通过主机端的数据缓存,提升文件访问性能。

#### **出题人**:阿里巴巴出题专家:起影/阿里云文件存储高级技术专家

#### **参考答案**:A,B,C




================================================
FILE: 01.阿里篇/1.1.9 输入 ping IP 后敲回车,发包前会发生什么?.md
================================================
#### **题目**:输入 ping IP 后敲回车,发包前会发生什么?

#### **出题人**:阿里巴巴出题专家:怀虎/阿里云云效平台负责人

#### **参考答案**:
ping目标ip时,先查路由表,确定出接口
- 如果落在直连接口子网内,此时若为以太网等 _多路访问网络_ 则先查询arp缓存,命中则直接发出,否则在该接口上发arp询问目标ip的mac地址,取得后发出,若为ppp等 _点对点网络_ ,则直接可以发出;
- 如果查表落在缺省路由上,此时若为以太网等 _多路访问网络_ 则先查询网关arp缓存,命中则直接发出,否则在该接口上发arp询问网关的mac地址,取得后发出,若为ppp等 _点对点网络_ ,则直接可以发出;
- 若查表未命中,则返回不可达。


================================================
FILE: 01.阿里篇/1.2.0 请解释下为什么鹿晗发布恋情的时候,微博系统会崩溃,如何解决?.md
================================================
#### **题目**:请解释下为什么鹿晗发布恋情的时候,微博系统会崩溃,如何解决?

#### **出题人**:阿里巴巴出题专家:江岚/阿里巴巴数据技术高级技术专家

#### **参考答案**:

A. 获取微博通过 pull 方式还是 push 方式

B. 发布微博的频率要远小于阅读微博

C. 流量明星的发微博,和普通博主要区分对待,比如在 sharding的时候,也要考虑这个因素



================================================
FILE: 01.阿里篇/1.2.1 现有一批邮件需要发送给订阅顾客,且有一个集群(集群的节点数不定,会动态扩容缩容)来负责具体的邮件发送任务,如何让系统尽快地完成发送?.md
================================================
#### **题目**:现有一批邮件需要发送给订阅顾客,且有一个集群(集群的节点数不定,会动态扩容缩容)来负责具体的邮件发送任务,如何让系统尽快地完成发送?请详述技术方案!

#### **出题人**:阿里巴巴出题专家:江岚/阿里巴巴数据技术高级技术专家

### **参考答案**:

A. 借助消息中间件,通过发布者订阅者模式来进行任务分配

B. master-slave 部署,由 master 来分配任务

C. 不借助任何中间件,且所有节点均等。通过数据库的 update-returning,从而实现节点之间任务的互斥


================================================
FILE: 01.阿里篇/1.2.2 有一批气象观测站,现需要获取这些站点的观测数据,并存储到 Hive 中。但是气象局只提供了 api 查询,每次只能查询单个观测点。那么如果能够方便快速地获取到所有的观测点的数据?.md
================================================
#### **题目**:有一批气象观测站,现需要获取这些站点的观测数据,并存储到 Hive 中。但是气象局只提供了 api 查询,每次只能查询单个观测点。那么如果能够方便快速地获取到所有的观测点的数据?

#### **出题人**:阿里巴巴出题专家:江岚/阿里巴巴数据技术高级技术专家

#### **参考答案**:

A. 通过 shell 或 python 等调用 api,结果先暂存本地,最后将本地文件上传到 Hive 中。

B. 通过 datax 的 httpReader 和 hdfsWriter 插件,从而获取所需的数据。

C. 比较理想的回答,是在计算引擎的 UDF 中调用查询 api,执行UDF 的查询结果存储到对应的表中。一方面,不需要同步任务的导出导入;另一方面,计算引擎的分布式框架天生提供了分布式、容错、并发等特性。

================================================
FILE: 01.阿里篇/1.2.3 如何实现两金额数据相加(最多小数点两位).md
================================================
#### **题目**:如何实现两金额数据相加(最多小数点两位)?

#### **出题人**:阿里巴巴出题专家:御术/蚂蚁金服数据可视化高级技术专家

#### **参考答案**:

其实问题并不难,就是考察候选人对 JavaScript 数据运算上的认知以及考虑问题的缜密程度,有很多坑,可以用在笔试题,如果用在面试,回答过程中还可以随机加入有很多计算机基础的延伸。

回到这个问题,由于直接浮点相与加会失精,所以要转整数;(可以插入问遇到过吗?是否可以举个例子?)。

转整数是第一个坑,虽然只有两位可以通过乘以100转整数,但由于乘以一百和除以一百都会出现浮点数的运算,所以也会失精,还是要通过字符串来转;(可以插入问字符串转整数有几种方式?)字符串转整是第二个坑,因为最后要对齐计算,如果没考虑周全先toFixed(2),对于只有一位小数点数据进入计算就会错误;转整数后的计算是个加分点,很多同学往往就是直接算了,如果可以考虑大数计算的场景,恭喜同学进入隐藏关卡,这就会涉及如何有效循环、遍历、算法复杂度的问题。

================================================
FILE: 01.阿里篇/1.2.4 关于并行计算的一些基础开放问题.md
================================================
#### **题目**: 关于并行计算的一些基础开放问题。

◼ 如何定义并计算,请分别阐述分布式内存到共享内存模式行编程的区别和实现(例子代码)?

◼ 请使用 MPI 和 OpenMP 分别实现 N 个处理器对 M 个变量的求和?

◼ 请说明 SIMD 指令在循环中使用的权限?向量化优化有哪些手段?

◼ 请用 Amdahl 定律说明什么是并行效率以及并行算法的扩展性?并说明扩展性的性能指标和限制因素,最后请说明在共享内存计算机中,共享内存的限制?OpenMP 是怎样实现共享内存编程环境的?MPI 阻塞和非阻塞读写的区别?

#### **出题人**:阿里巴巴出题专家:何万青/阿里云高性能计算资深技术专家

#### **参考答案**:

(简要答案,但必须触及,可以展开)
◼ 同时执行多个/算法/逻辑操作/内存访问/IO,相互独立同时运行,分三个层次:进程级,多个节点分布式内存通过MPI通信并行;线程级,共享内存的多路机器,通过OpenMP实现多线程并行;指令集:通过SIM指令实现单指令多数据。。。。举例吧啦吧啦。

◼ MPI代码,,,OpenMP代码,分别写出来 M个元素,N个处理器的累加,后者注意private 参数。

◼ SIMD在循环中的应用,限制在于 SIMD指令处理的每一个数组的长度,cache line利用,内部循环间的依赖和条件调用等。

◼ 向量化,主要看SSE和AVX指令占比率,通过编译器优化...... 在loop代码中使用。

◼ 性能和计算规模随处理器增加的变化曲线,实测HPL和峰值HPL比率,能用用Amdahl定律表达Tpar(N) = (an + (1-a)n/N )t + C (n,N), 能够讲明白串行部分对整个并行的天花板效应,扩展性能够解释清楚算法的扩展性=并行效率随处理器数目的变化关系,画出来。

◼ 共享内存计算机OpenMP对变量的限制描述,EREW,CREW,ERCW,CRCW等区别,NUMA概念,如何保持coherent等。

◼ 写出OpenMP和MPI的核心函数,回答问题即可。

================================================
FILE: 01.阿里篇/1.2.5 请计算XILINX公司VU9P芯片的算力相当于多少TOPS,给出计算过程与公式.md
================================================
#### **题目**:请计算XILINX公司VU9P芯片的算力相当于多少TOPS,给出计算过程与公式。

#### **出题人**: 阿里巴巴出题专家:隐达/阿里云异构计算资深专家

#### **参考答案**:基于不同的算法,这个值在十几到几百之间。但是,如果只是单纯比算力,FPGA和ASIC、GPU相比并无太大优势,甚至大多时候有较大劣势。FPGA的优势在于高度的灵活性和算法的针对性。

================================================
FILE: 01.阿里篇/1.2.6 一颗现代处理器,每秒大概可以执行多少条简单的MOV指令,有哪些主要的影响因素.md
================================================
#### **题目**:一颗现代处理器,每秒大概可以执行多少条简单的MOV指令,有哪些主要的影响因素?

#### **出题人**:阿里巴巴出题专家:子团/创新产品虚拟化&稳定性资深技术专家

#### **参考答案**:

**及格:**
每执行一条mov指令需要消耗1个时钟周期,所以每秒执行的mov指令和CPU主频相关。

**加分:**
在CPU微架构上,要考虑数据预取,乱序执行,多发射,内存stall(前端stall和后端stall)等诸多因素,因此除了cpu主频外,还和流水线上的效率(IPC)强相关,比较复杂的一个问题。

================================================
FILE: 01.阿里篇/1.2.7 请分析 MaxCompute 产品与分布式技术的关系、当前大数据计算平台类产品的市场现状和发展趋势.md
================================================
#### **题目**:请分析 MaxCompute 产品与分布式技术的关系、当前大数据计算平台类产品的市场现状和发展趋势。

#### **出题人**:阿里巴巴出题专家:云郎/阿里 MaxCompute 高级产品专家

#### **参考答案**:

开放性问题,无标准答案。

================================================
FILE: 01.阿里篇/1.2.8 对大数据平台中的元数据管理是怎么理解的,元数据收集管理体系是怎么样的,会对大数据应用有什么样的影响.md
================================================
#### **题目**: 对大数据平台中的元数据管理是怎么理解的,元数据收集管理体系是怎么样的,会对大数据应用有什么样的影响。

#### **出题人**: 阿里巴巴出题专家:映泉/阿里巴巴高级技术专家

#### **参考答案**:开放性问题,无标准答案。



================================================
FILE: 01.阿里篇/1.2.9 你理解常见如阿里,和友商大数据平台的技术体系差异以及发展趋势和技术瓶颈,在存储和计算两个方面进行概述.md
================================================
#### **题目**: 你理解常见如阿里,和友商大数据平台的技术体系差异以及发展趋势和技术瓶颈,在存储和计算两个方面进行概述。

#### **出题人**: 阿里巴巴出题专家:映泉/阿里巴巴高级技术专家

#### **参考答案**:开放性问题,无标准答案。

================================================
FILE: 01.阿里篇/1.3.0 在云计算大数据处理场景中,每天运行着成千上万的任务,每个任务都要进行 IO 读写.md
================================================
#### **题目**:在云计算大数据处理场景中,每天运行着成千上万的任务,每个任务都要进行 IO 读写。存储系统为了更好的服务,经常会保证高优先级的任务优先执行。当多个作业或用户访问存储系统时,如何保证优先级和公平性。

#### **出题人**:阿里巴巴出题专家:田磊磊/阿里云文件存储高级技术专家

#### **参考答案**:开放性问题,无标准答案。

================================================
FILE: 01.阿里篇/1.3.1 最大频率栈.md
================================================
#### **题目**:最大频率栈。
实现 FreqStack,模拟类似栈的数据结构的操作的一个类。FreqStack 有两个函数:
push(int x),将整数 x 推入栈中。pop(),它移除并返回栈中出现最频繁的元素。如果最频繁的元素不只一个,则移除并返回最接近栈顶的元素。
◼ 示例:
push [5,7,5,7,4,5]
pop() -> 返回 5,因为 5 是出现频率最高的。
栈变成
[5,7,5,7,4]。
pop() -> 返回 7,因为 5 和 7 都是频率最高的,但 7 最接近栈
顶。
栈变成 [5,7,5,4]。
pop() -> 返回 5 。
栈变成 [5,7,4]。
pop() -> 返回 4 。
栈变成 [5,7]。

#### **出题人**:阿里巴巴出题专家:屹平/阿里云视频云边缘计算高级技术专家

#### **参考答案**:

令 freq 作为 x 的出现次数的映射 Map。

此外 maxfreq,即栈中任意元素的当前最大频率,因为我们必须弹出频率最高的元素。

当前主要的问题就变成了:在具有相同的(最大)频率的元素中,怎么判断那个元素是最新的?我们可以使用栈来查询这一信息:靠近栈顶的元素总是相对更新一些。

为此,我们令 group 作为从频率到具有该频率的元素的映射。到目前,我们已经实现了 FreqStack 的所有必要的组件。

算法:

实际上,作为实现层面上的一点细节,如果 x 的频率为 f,那么我们将获取在所有 group[i] (i <= f) 中的 x,而不仅仅是栈顶的那个。这是因为每个 group[i] 都会存储与第 i 个 x 副本相关的信息。

最后,我们仅仅需要如上所述维持 freq,group,以及 maxfreq。

**参考代码***:
```
class FreqStack {
    Map<Integer, Integer> freq;
    Map<Integer, Stack<Integer>> group;
    int maxfreq;

    public FreqStack() {
        freq = new HashMap();
        group = new HashMap();
        maxfreq = 0;
    }
    
    public void push(int x) {
        int f = freq.getOrDefault(x, 0) + 1;
        freq.put(x, f);
        if (f > maxfreq) maxfreq = f;
        group.computeIfAbsent(f, z-> new Stack()).push(x);
    }
    
    public int pop() {
        int x = group.get(maxfreq).pop();
        freq.put(x, freq.get(x) - 1);
        if (group.get(maxfreq).size() == 0)
        maxfreq--;
        return x;
    }
}
```

================================================
FILE: 01.阿里篇/1.3.2 给定一个链表,删除链表的倒数第N个节点,并且返回链表的头结点.md
================================================
#### **题目**:给定一个链表,删除链表的倒数第 N 个节点,并且返回链表的头结点。

◼ 示例:
给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.
说明:
给定的 n 保证是有效的。
要求:
只允许对链表进行一次遍历。

#### **出题人**:阿里巴巴出题专家:屹平/阿里云视频云边缘计算高级技术专家

#### **参考答案**:

我们可以使用两个指针而不是一个指针。第一个指针从列表的开头向前移动 n+1 步,而第二个指针将从列表的开头出发。现在,这两个指针被 n 个结点分开。我们通过同时移动两个指针向前来保持这个恒定的间隔,直到第一个指针到达最后一个结点。此时第二个指针将指向从最后一个结点数起的第 n 个结点。我们重新链接第二个指针所引用的结点的 next 指针指向该结点的下下个结点。

**参考代码**:

```
public ListNode removeNthFromEnd(ListNode head, int n)
{
    ListNode dummy = new ListNode(0);
    dummy.next = head;
    ListNode first = dummy;
    ListNode second = dummy;
    // Advances first pointer so that the gap between first
    and second is n nodes apart
    for (int i = 1; i <= n + 1; i++) {
        first = first.next;
    }
    // Move first to the end, maintaining the gap
    while (first != null) {
        first = first.next;
        second = second.next;
    }
    second.next = second.next.next;
    return dummy.next;
}
```

**复杂度分析:**
* 时间复杂度:O(L),该算法对含有 L 个结点的列表进行了一次遍历。因此时间复杂度为 O(L)。

* 空间复杂度:O(1),我们只用了常量级的额外空间。


================================================
FILE: 01.阿里篇/1.3.3 如果让你设计一个通用的、支持各种数据库秒级备份和恢复的系统,你会如何设计.md
================================================
#### **题目**:如果让你设计一个通用的、支持各种数据库秒级备份和恢复的系统,你会如何设计?

#### **出题人**:阿里巴巴出题专家:千震/阿里云数据库高级技术专家

#### **参考答案**:开放性问题,无标准答案。

================================================
FILE: 01.阿里篇/1.3.4 如果让你来设计一个支持数据库、NOSQL 和大数据之间数据实时流动的数据流及处理的系统,你会考虑哪些问题?如何设计?.md
================================================
#### **题目**:如果让你来设计一个支持数据库、NOSQL 和大数据之间数据实时流动的数据流及处理的系统,你会考虑哪些问题?如何设计?

#### **出题人**:阿里巴巴出题专家:千震/阿里云数据库高级技术专家

#### **参考答案**:开放性问题,无标准答案。

================================================
FILE: 01.阿里篇/1.3.5 给定一个整数数组和一个整数,返回两个数组的索引,这两个索引指向的数字的加和等于指定的整数。需要最优的算法,分析算法的空间和时间复杂度.md
================================================

题目:给定一个整数数组和一个整数,返回两个数组的索引,这两个索引指向的数字的加和等于指定的整数。需要最优的算法,分析算法的空间和时间复杂度

参考答案:
```Java
public int[] twoSum(int[] nums, int target) {
    if(nums==null || nums.length<2)
        return new int[]{0,0};
 
    HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
    for(int i=0; i<nums.length; i++){
        if(map.containsKey(nums[i])){
            return new int[]{map.get(nums[i]), i};
        }else{
            map.put(target-nums[i], i);
        }
    }
 
    return new int[]{0,0};
}
```
分析:空间复杂度和时间复杂度均为 O(n)


================================================
FILE: 01.阿里篇/1.3.6 假如给你一个新产品,你将从哪些方面来保障它的质量?.md
================================================
#### **题目**:假如给你一个新产品,你将从哪些方面来保障它的质量?

#### **出题人**:阿里巴巴出题专家:晨晖 /阿里云中间件技术部测试开发专家

#### **参考答案**:

可以从代码开发、测试保障、线上质量三个方面来保障。

在代码开发阶段,有单元测试、代码Review、静态代码扫描等;

测试保障阶段,有功能测试、性能测试、高可用测试、稳定性测试、兼容性测试等;

在线上质量方面,有灰度发布、紧急回滚、故障演练、线上监控和巡检等。

================================================
FILE: 01.阿里篇/1.3.7 请评估一下程序的执行结果?.md
================================================
#### **题目**:请评估一下程序的执行结果?
```
public class SynchronousQueueQuiz {
    public static void main(String[] args) throws Exception {
        BlockingQueue<Integer> queue = new
        SynchronousQueue<>();
        System. out .print(queue.offer(1) + " ");
        System. out .print(queue.offer(2) + " ");
        System. out .print(queue.offer(3) + " ");
        System. out .print(queue.take() + " ");
        System. out .println(queue.size());
    }
}

```
A. true true true 1 3

B. true true true (阻塞)

C. false false false null 0

D. false false false (阻塞)

#### **出题人**:阿里巴巴出题专家:桃谷/阿里云中间件技术专家

#### **参考答案**:D



================================================
FILE: 01.阿里篇/1.3.8 如何测试一辆自行车.md
================================================
#### **题目**:如何测试一辆自行车
#### **出题人**:阿里巴巴新零售技术质量部

#### **参考答案**:

开放性问题,如果联系到测试角度上看的话,可以这么作答:

1. 骑车试一试,看有没有问题,对应测试能不能跑通
2. 看看车的核心部件,例如车闸,车把等,对应软件的核心功能
3. 看看车是否安全,配锁质量如何,对应软件是否有安全问题



================================================
FILE: 01.阿里篇/1.3.9 如何判断两个链表是否相交.md
================================================
#### **题目**:如何判断两个链表是否相交

#### **出题人**:阿里巴巴新零售技术质量部

#### **参考答案**:

$O(n^2)$: 两层遍历,总能发现是否相交

$O(n)$: 一层遍历,遍历完两个链表,如果两个链表的最后一个结点指针相同,则相交,否则不相交



================================================
FILE: 02.华为篇/.gitkeep
================================================


================================================
FILE: 02.华为篇/2.1.1 static有什么用途?(请至少说明两种).md
================================================
#### **题目**: static有什么用途?(请至少说明两种)

#### **参考答案**: 
1) 在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
2) 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
3) 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用


================================================
FILE: 02.华为篇/2.1.2 引用与指针有什么区别?.md
================================================
#### **题目**:引用与指针有什么区别?

#### **参考答案**: 
1) 引用必须被初始化,指针不必。
2) 引用初始化以后不能被改变,指针可以改变所指的对象。
3) 不存在指向空值的引用,但是存在指向空值的指针。


================================================
FILE: 02.华为篇/2.1.3 描述实时系统的基本特性.md
================================================
#### **题目**:描述实时系统的基本特性

#### **参考答案**: 
在特定时间内完成特定的任务,实时性与可靠性。


================================================
FILE: 02.华为篇/2.1.4 全局变量和局部变量在内存中是否有区别?如果有,是什么区别?.md
================================================
#### **题目**:全局变量和局部变量在内存中是否有区别?如果有,是什么区别?


#### **参考答案**: 
全局变量储存在静态数据库,局部变量在堆栈。


================================================
FILE: 02.华为篇/2.1.5 什么是平衡二叉树?.md
================================================
#### **题目**:什么是平衡二叉树?

#### **参考答案**: 
左右子树都是平衡二叉树 且左右子树的深度差值的绝对值不大于1。

================================================
FILE: 02.华为篇/2.1.6 堆栈溢出一般是由什么原因导致的?.md
================================================
#### **题目**:堆栈溢出一般是由什么原因导致的?

#### **参考答案**: 
没有回收垃圾资源。

================================================
FILE: 02.华为篇/2.1.7 什么函数不能声明为虚函数?.md
================================================
#### **题目**:什么函数不能声明为虚函数?

#### **参考答案**: 
constructor函数不能声明为虚函数。

================================================
FILE: 02.华为篇/2.1.8 冒泡排序算法的时间复杂度是什么?.md
================================================
#### **题目**: 冒泡排序算法的时间复杂度是什么?

#### **参考答案**: 
时间复杂度是O(n^2)。

================================================
FILE: 02.华为篇/2.1.9. Internet采用哪种网络协议?该协议的主要层次结构?.md
================================================
#### **题目**:Internet采用哪种网络协议?该协议的主要层次结构?


#### **参考答案**: 
Tcp/Ip协议
主要层次结构为: 应用层/传输层/网络层/数据链路层/物理层。


================================================
FILE: 02.华为篇/2.2.0 IP地址的编码分为哪俩部分?.md
================================================
#### **题目**:IP地址的编码分为哪俩部分?

#### **参考答案**: 
IP地址由两部分组成,网络号和主机号。不过是要和“子网掩码”按位与上之后才能区分哪些是网络位哪些是主机位。




================================================
FILE: 02.华为篇/2.2.1 用户输入M,N值,从1至N开始顺序循环数数,每数到M输出该数值,直至全部输出。写出C程序.md
================================================
#### **题目**:用户输入M,N值,从1至N开始顺序循环数数,每数到M输出该数值,直至全部输出。写出C程序。

#### **参考答案**:
循环链表,用取余操作做

================================================
FILE: 02.华为篇/2.2.2 某32位系统下, C++程序,请计算sizeof 的值.md
================================================
#### **题目**:某32位系统下, C++程序,请计算sizeof 的值.


#### **参考答案**:
```
char str[] = “http://www.ibegroup.com/”
char *p = str ;
int n = 10;
请计算
sizeof (str ) = ?(1)
sizeof ( p ) = ?(2)
sizeof ( n ) = ?(3)
void Foo ( char str[100]){
请计算
sizeof( str ) = ?(4)
}
void *p = malloc( 100 );
请计算
sizeof ( p ) = ?(5)

(1)17 (2)4 (3) 4 (4)4 (5)4
```


================================================
FILE: 03.百度篇/.gitkeep
================================================


================================================
FILE: 03.百度篇/3.4.8 找出数组中出现次数超过一半的数,现在有一个数组,已知一个数出现的次数超过了一半,请用O(n)的复杂度的算法找出这个数。.md
================================================
#### **题目**:找出数组中出现次数超过一半的数,现在有一个数组,已知一个数出现的次数超过了一半,请用O(n)的复杂度的算法找出这个数。

#### **出题人**:阿里巴巴新零售技术质量部

#### **参考答案**:

遍历数组的同时用Object来储存每个元素出现的个数,每次遍历都跟max比较

#### **参考代码**:
```
const arr =[1,2,3,4,5,6,7,8,89,9,9,9,9,99];

const maxNumObj =(arr)=>{
    return arr.reduce((obj,cur)=>{
        if(!obj['max']){
            obj['max']=arr[0];
        }
        obj[cur] = ++obj[cur]||1;
        if(obj[cur]>obj['max']){
          obj['max'] = obj[cur]
        }
        return obj;
    },{})['max'];
}
```


================================================
FILE: 03.百度篇/3.4.9 找出被修改过的数字.md
================================================
#### **题目**: 找出被修改过的数字

#### **出题人**:阿里巴巴新零售技术质量部

#### **参考答案**:

emmm假设背景是寻找数组中被修改的数字或者元素,我使用代理的方式来监听数组中元素的变化,并将变化的最后数值和次数储存在额外空间中

#### **参考代码**:

```
const arr =[1,2,3,4,5,6,7,8,89,9,9,9,9,99];

const saveModifyNum={

}
const watchNumChangeProxy=new Proxy(arr,{
    set:function(target,key,value){
        let {count} = Reflect.get(saveModifyNum,target[key])||{count:0,value};
        ++count;
        return Reflect.set(saveModifyNum,target[key],{
            count,
            value
        });    
    }
})
watchNumChangeProxy[0]=2;
watchNumChangeProxy[0]=3;
watchNumChangeProxy[0]=4;
//{ '1': { count: 3, value: 4 } }
```





================================================
FILE: 04.腾讯篇/.gitkeep
================================================


================================================
FILE: 05.美团篇/.gitkeep
================================================


================================================
FILE: 05.美团篇/5.2.0 浅复制和深复制?怎样实现深复制?.md
================================================
#### **题目**:浅复制和深复制?怎样实现深复制?

#### **出题人**:阿里巴巴新零售技术质量部

#### **参考答案**:

#### **参考代码**;
```
const isObject = (item)=>{
  return Object.prototype.toString.call(item) === '[object Object]';
}
const isArray = (item)=>{
  return Object.prototype.toString.call(item) === '[object Array]';
}

const deepClone=(obj)=>{
    const cloneObj=isArray(obj)?[]:isObject(obj)?{}:'';
    for(let key in obj){
        if(isObject(obj[key])||isArray(obj[key])){
          Object.assign(cloneObj,{
           [key]: deepClone(Reflect.get(obj,key))
          });
        }
        else{
          cloneObj[key] = obj[key];
        }  
    }
    return cloneObj;
}
```

PS:可以处理这样的格式,仅处理了对象类型和数组类型
```
const obj111 ={
  a:1,
  b:{
    c:2,
    d:{
      e:3
    },
    f:[1,{a:1,b:2},3]
  }
}
```



================================================
FILE: 06.头条篇/.gitkeep
================================================


================================================
FILE: 06.头条篇/一棵二叉树,求最大通路长度(即最大左右子树高度之和).md
================================================
#### **题目**:一棵二叉树,求最大通路长度(即最大左右子树高度之和)  

#### **参考答案**:  

该题与leetcode第104题同题型,定义TreeNode结构如下:  

```java
class TreeNode {

    int val;
    TreeNode left;
    TreeNode right;

    public TreeNode(int val) {
        this.val = val;
    }
}
```

解法一(递归求解)   
```java
class Solution {

    public int maxHeight(TreeNode root) {
        if (root == null) {
            return 0;
        }
        return maxChildHeight(root.left) + maxChildHeight(root.right);
    }

    public int maxChildHeight(TreeNode root) {
        if (root == null) {
            return 0;
        }
        int leftHeight = maxChildHeight(root.left);
        int rightHeight = maxChildHeight(root.right);
        return Math.max(leftHeight, rightHeight) + 1;
    }
}
```

解法二(迭代求解)
```java
public class Solution {

    public int maxHeight(TreeNode root) {
        if (root == null) {
            return 0;
        }
        return maxChildHeight(root.left) + maxChildHeight(root.right);
    }

    public int maxChildHeight(TreeNode root) {
        int height = 0;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);

        while (!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                TreeNode node = queue.poll();
                height++;
                if (node.left != null) {
                    queue.add(node.left);
                }
                if (node.right != null) {
                    queue.add(node.right);
                }
            }
        }
        return height;
    }
}
```


================================================
FILE: 07.滴滴篇/.gitkeep
================================================


================================================
FILE: 08.京东篇/.gitkeep
================================================


================================================
FILE: 08.京东篇/简单说一下hadoop和spark的shuffle过程.md
================================================
##### 问题:**简单说一下hadoop和spark的shuffle过程**

##### 出题人:京东出题专家:阿昀/京东数据中台

##### 参考答案:

Hadoop:map端保存分片数据,通过网络收集到reduce端。

Spark:spark的shuffle实在DAGSchedular划分Stage的时候产生的,TaskSchedular要分发Stage到各个worker的executor。减少shuffle可以提高性能。







================================================
FILE: 09.MySQL篇/.gitkeep
================================================


================================================
FILE: 09.MySQL篇/9.1.0 主键 超键 候选键 外键.md
================================================
#### **题目**:主键 超键 候选键 外键是什么

#### 定义

**超键(super key)**: 在关系中能唯一标识元组的属性集称为关系模式的超键

**候选键(candidate key)**: 不含有多余属性的超键称为候选键。也就是在候选键中,若再删除属性,就不是键了!

**主键(primary key)**: 用户选作元组标识的一个候选键程序主键

**外键(foreign key)**:如果关系模式R中属性K是其它模式的主键,那么k在模式R中称为外键。

#### 举例

比如有如下数据:

| 学号 | 姓名 | 性别 | 年龄 | 系别 | 专业 
|:---:|:---:|:---:|:---:|:---:|:---:
|20020612 |李辉 |男 |20 |计算机 |软件开发 
|20060613| 张明| 男 |18 |计算机 |软件开发 
|20060614| 王小玉| 女 |19 |物理 |力学 
|20060615| 李淑华| 女 |17 |生物 |动物学 
|20060616| 赵静| 男 |21 |化学 |食品化学 
|20060617| 赵静| 女 |20 |生物 |植物学

1. 超键
   
    在关系中能唯一标识元组的属性集称为关系模式的超键。

    于是我们从例子中可以发现 学号是标识学生实体的唯一标识。那么该元组的超键就为学号。

    除此之外我们还可以把它跟其他属性组合起来,比如:

    (`学号`,`性别`)

    (`学号`,`年龄`)

    这样也是超键.

2. 候选键

    不含多余属性的超键为候选键。

    根据例子可知,学号是一个可以唯一标识元组的唯一标识,因此学号是一个候选键,实际上,候选键是超键的子集,比如 (学号,年龄)是超键,但是它不是候选键。因为它还有了额外的属性。

3. 主键

    用户选择的候选键作为该元组的唯一标识,那么它就为主键。

    简单的说,例子中的元组的候选键为学号,但是我们选定他作为该元组的唯一标识,那么学号就为主键。

4. 外键

    外键是相对于主键的,比如在学生记录里,主键为学号,在成绩单表中也有学号字段,因此学号为成绩单表的外键,为学生表的主键。

#### 总结

**主键为候选键的子集,候选键为超键的子集,而外键的确定是相对于主键的。**

================================================
FILE: 09.MySQL篇/9.1.1 数据库事务的四个特性及含义.md
================================================
#### **题目**:数据库事务的四个特性及含义

#### **参考答案**:

数据库事务transanction正确执行的四个基本要素。ACID,原子性(Atomicity)、一致性(Correspondence)、隔离性(Isolation)、持久性(Durability)。

* 原子性:整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。

* 一致性:在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。

* 隔离性:隔离状态执行事务,使它们好像是系统在给定时间内执行的唯一操作。如果有两个事务,运行在相同的时间内,执行 相同的功能,事务的隔离性将确保每一事务在系统中认为只有该事务在使用系统。这种属性有时称为串行化,为了防止事务操作间的混淆,必须串行化或序列化请 求,使得在同一时间仅有一个请求用于同一数据。

* 持久性:在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。

================================================
FILE: 09.MySQL篇/9.1.2 视图的作用,视图可以更改么?.md
================================================
#### **题目**:视图的作用,视图可以更改么?

#### **参考答案**:

视图是虚拟的表,与包含数据的表不一样,视图只包含使用时动态检索数据的查询;不包含任何列或数据。使用视图可以简化复杂的sql操作,隐藏具体的细节,保护数据;视图创建后,可以使用与表相同的方式利用它们。

视图不能被索引,也不能有关联的触发器或默认值,如果视图本身内有order by 则对视图再次order by将被覆盖。

创建视图:create view XXX as XXXXXXXXXXXXXX;

对于某些视图比如未使用联结子查询分组聚集函数Distinct Union等,是可以对其更新的,对视图的更新将对基表进行更新;但是视图主要用于简化检索,保护数据,并不用于更新,而且大部分视图都不可以更新。

================================================
FILE: 09.MySQL篇/9.1.3 drop,delete与truncate的区别.md
================================================
#### **题目**:drop,delete与truncate的区别

#### **参考答案**:

drop直接删掉表 truncate删除表中数据,再插入时自增长id又从1开始 delete删除表中数据,可以加where字句。

(1) DELETE语句执行删除的过程是每次从表中删除一行,并且同时将该行的删除操作作为事务记录在日志中保存以便进行进行回滚操作。TRUNCATE TABLE 则一次性地从表中删除所有的数据并不把单独的删除操作记录记入日志保存,删除行是不能恢复的。并且在删除的过程中不会激活与表有关的删除触发器。执行速度快。

(2) 表和索引所占空间。当表被TRUNCATE 后,这个表和索引所占用的空间会恢复到初始大小,而DELETE操作不会减少表或索引所占用的空间。drop语句将表所占用的空间全释放掉。

(3) 一般而言,drop > truncate > delete

(4) 应用范围。TRUNCATE 只能对TABLE;DELETE可以是table和view

(5) TRUNCATE 和DELETE只删除数据,而DROP则删除整个表(结构和数据)。

(6) truncate与不带where的delete :只删除数据,而不删除表的结构(定义)drop语句将删除表的结构被依赖的约束(constrain),触发器(trigger)索引(index);依赖于该表的存储过程/函数将被保留,但其状态会变为:invalid。

(7) delete语句为DML(Data Manipulation Language),这个操作会被放到 rollback segment中,事务提交后才生效。如果有相应的 tigger,执行的时候将被触发。

(8) truncate、drop是DDL(Data Define Language),操作立即生效,原数据不放到 rollback segment中,不能回滚

(9) 在没有备份情况下,谨慎使用 drop 与 truncate。要删除部分数据行采用delete且注意结合where来约束影响范围。回滚段要足够大。要删除表用drop;若想保留表而将表中数据删除,如果于事务无关,用truncate即可实现。如果和事务有关,或老是想触发trigger,还是用delete。

(10) Truncate table 表名 速度快,而且效率高,因为: truncate table 在功能上与不带 WHERE 子句的 DELETE 语句相同:二者均删除表中的全部行。但 TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少。DELETE 语句每次删除一行,并在事务日志中为所删除的每行记录一项。TRUNCATE TABLE 通过释放存储表数据所用的数据页来删除数据,并且只在事务日志中记录页的释放。

(11) TRUNCATE TABLE 删除表中的所有行,但表结构及其列、约束、索引等保持不变。新行标识所用的计数值重置为该列的种子。如果想保留标识计数值,请改用 DELETE。如果要删除表定义及其数据,请使用 DROP TABLE 语句。

(12) 对于由 FOREIGN KEY 约束引用的表,不能使用 TRUNCATE TABLE,而应使用不带 WHERE 子句的 DELETE 语句。由于 TRUNCATE TABLE 不记录在日志中,所以它不能激活触发器。


================================================
FILE: 09.MySQL篇/9.1.4 索引的工作原理及其种类.md
================================================
#### **题目**:索引的工作原理及其种类

#### **参考答案**:

**数据库索引**,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用B树及其变种B+树。

在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。

为表设置索引要付出代价的:一是增加了数据库的存储空间,二是在插入和修改数据时要花费较多的时间(因为索引也要随之变动)。

<img src=http://www.2cto.com/uploadfile/Collfiles/20150416/2015041610033731.png> </img>

图展示了一种可能的索引方式。左边是数据表,一共有两列七条记录,最左边的是数据记录的物理地址(注意逻辑上相邻的记录在磁盘上也并不是一定物理相邻的)。为了加快Col2的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找在O(log2n)的复杂度内获取到相应数据。

创建索引可以大大提高系统的性能。

第一,通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。

第二,可以大大加快数据的检索速度,这也是创建索引的最主要的原因。

第三,可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。

第四,在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。

第五,通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。

也许会有人要问:增加索引有如此多的优点,为什么不对表中的每一个列创建一个索引呢?因为,增加索引也有许多不利的方面。

第一,创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。

第二,索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。

第三,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。

索引是建立在数据库表中的某些列的上面。在创建索引的时候,应该考虑在哪些列上可以创建索引,在哪些列上不能创建索引。一般来说,应该在这些列上创建索引:在经常需要搜索的列上,可以加快搜索的速度;在作为主键的列上,强制该列的唯一性和组织表中数据的排列结构;在经常用在连接的列上,这些列主要是一些外键,可以加快连接的速度;在经常需要根据范围进行搜索的列上创建索引,因为索引已经排序,其指定的范围是连续的;在经常需要排序的列上创建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间;在经常使用在WHERE子句中的列上面创建索引,加快条件的判断速度。

同样,对于有些列不应该创建索引。一般来说,不应该创建索引的的这些列具有下列特点:

第一,对于那些在查询中很少使用或者参考的列不应该创建索引。这是因为,既然这些列很少使用到,因此有索引或者无索引,并不能提高查询速度。相反,由于增加了索引,反而降低了系统的维护速度和增大了空间需求。

第二,对于那些只有很少数据值的列也不应该增加索引。这是因为,由于这些列的取值很少,例如人事表的性别列,在查询的结果中,结果集的数据行占了表中数据行的很大比例,即需要在表中搜索的数据行的比例很大。增加索引,并不能明显加快检索速度。

第三,对于那些定义为text, image和bit数据类型的列不应该增加索引。这是因为,这些列的数据量要么相当大,要么取值很少。

第四,当修改性能远远大于检索性能时,不应该创建索引。这是因为,修改性能和检索性能是互相矛盾的。当增加索引时,会提高检索性能,但是会降低修改性能。当减少索引时,会提高修改性能,降低检索性能。因此,当修改性能远远大于检索性能时,不应该创建索引。

根据数据库的功能,可以在数据库设计器中创建三种索引:唯一索引、主键索引和聚集索引。

唯一索引

唯一索引是不允许其中任何两行具有相同索引值的索引。

当现有数据中存在重复的键值时,大多数数据库不允许将新创建的唯一索引与表一起保存。数据库还可能防止添加将在表中创建重复键值的新数据。例如,如果在employee表中职员的姓(lname)上创建了唯一索引,则任何两个员工都不能同姓。 主键索引 数据库表经常有一列或列组合,其值唯一标识表中的每一行。该列称为表的主键。 在数据库关系图中为表定义主键将自动创建主键索引,主键索引是唯一索引的特定类型。该索引要求主键中的每个值都唯一。当在查询中使用主键索引时,它还允许对数据的快速访问。 聚集索引 在聚集索引中,表中行的物理顺序与键值的逻辑(索引)顺序相同。一个表只能包含一个聚集索引。

如果某索引不是聚集索引,则表中行的物理顺序与键值的逻辑顺序不匹配。与非聚集索引相比,聚集索引通常提供更快的数据访问速度。

局部性原理与磁盘预读
由于存储介质的特性,磁盘本身存取就比主存慢很多,再加上机械运动耗费,磁盘的存取速度往往是主存的几百分分之一,因此为了提高效率,要尽量减少磁盘I/O。为了达到这个目的,磁盘往往不是严格按需读取,而是每次都会预读,即使只需要一个字节,磁盘也会从这个位置开始,顺序向后读取一定长度的数据放入内存。这样做的理论依据是计算机科学中著名的局部性原理:当一个数据被用到时,其附近的数据也通常会马上被使用。程序运行期间所需要的数据通常比较集中。

由于磁盘顺序读取的效率很高(不需要寻道时间,只需很少的旋转时间),因此对于具有局部性的程序来说,预读可以提高I/O效率。

预读的长度一般为页(page)的整倍数。页是计算机管理存储器的逻辑块,硬件及操作系统往往将主存和磁盘存储区分割为连续的大小相等的块,每个存储块称为一页(在许多操作系统中,页得大小通常为4k),主存和磁盘以页为单位交换数据。当程序要读取的数据不在主存中时,会触发一个缺页异常,此时系统会向磁盘发出读盘信号,磁盘会找到数据的起始位置并向后连续读取一页或几页载入内存中,然后异常返回,程序继续运行。

B-/+Tree索引的性能分析
到这里终于可以分析B-/+Tree索引的性能了。

上文说过一般使用磁盘I/O次数评价索引结构的优劣。先从B-Tree分析,根据B-Tree的定义,可知检索一次最多需要访问h个节点。数据库系统的设计者巧妙利用了磁盘预读原理,将一个节点的大小设为等于一个页,这样每个节点只需要一次I/O就可以完全载入。为了达到这个目的,在实际实现B-Tree还需要使用如下技巧:

每次新建节点时,直接申请一个页的空间,这样就保证一个节点物理上也存储在一个页里,加之计算机存储分配都是按页对齐的,就实现了一个node只需一次I/O。

B-Tree中一次检索最多需要h-1次I/O(根节点常驻内存),渐进复杂度为O(h)=O(logdN)。一般实际应用中,出度d是非常大的数字,通常超过100,因此h非常小(通常不超过3)。

而红黑树这种结构,h明显要深的多。由于逻辑上很近的节点(父子)物理上可能很远,无法利用局部性,所以红黑树的I/O渐进复杂度也为O(h),效率明显比B-Tree差很多。

综上所述,用B-Tree作为索引结构效率是非常高的。

================================================
FILE: 09.MySQL篇/9.1.5 连接的种类.md
================================================
#### **题目**:连接的种类

#### **参考答案**:

查询分析器中执行:
```
--建表table1,table2:
create table table1(id int,name varchar(10))
create table table2(id int,score int)
insert into table1 select 1,'lee'
insert into table1 select 2,'zhang'
insert into table1 select 4,'wang'
insert into table2 select 1,90
insert into table2 select 2,100
insert into table2 select 3,70

```
如表:
```
-------------------------------------------------
table1 | table2 |
-------------------------------------------------
id name |id score |
1 lee |1 90|
2 zhang| 2 100|
4 wang| 3 70|
-------------------------------------------------
```
以下均在查询分析器中执行
一、外连接
1.概念:包括左向外联接、右向外联接或完整外部联接

2.左连接:left join 或 left outer join
(1)左向外联接的结果集包括 LEFT OUTER 子句中指定的左表的所有行,而不仅仅是联接列所匹配的行。如果左表的某行在右表中没有匹配行,则在相关联的结果集行中右表的所有选择列表列均为空值(null)。
(2)sql 语句
```
select * from table1 left join table2 on table1.id=table2.id
-------------结果-------------
idnameidscore
------------------------------
1lee190
2zhang2100
4wangNULLNULL
------------------------------
```
注释:包含table1的所有子句,根据指定条件返回table2相应的字段,不符合的以null显示

3.右连接:right join 或 right outer join
(1)右向外联接是左向外联接的反向联接。将返回右表的所有行。如果右表的某行在左表中没有匹配行,则将为左表返回空值。
(2)sql 语句
```
select * from table1 right join table2 on table1.id=table2.id
-------------结果-------------
idnameidscore
------------------------------
1lee190
2zhang2100
NULLNULL370
------------------------------
```
注释:包含table2的所有子句,根据指定条件返回table1相应的字段,不符合的以null显示

4.完整外部联接:full join 或 full outer join
(1)完整外部联接返回左表和右表中的所有行。当某行在另一个表中没有匹配行时,则另一个表的选择列表列包含空值。如果表之间有匹配行,则整个结果集行包含基表的数据值。
(2)sql 语句
```
select * from table1 full join table2 on table1.id=table2.id
-------------结果-------------
idnameidscore
------------------------------
1lee190
2zhang2100
4wangNULLNULL
NULLNULL370
------------------------------
```
注释:返回左右连接的和(见上左、右连接)

二、内连接
1.概念:内联接是用比较运算符比较要联接列的值的联接

2.内连接:join 或 inner join

3.sql 语句
```
select * from table1 join table2 on table1.id=table2.id
-------------结果-------------
idnameidscore
------------------------------
1lee190
2zhang2100
------------------------------
```
注释:只返回符合条件的table1和table2的列

4.等价(与下列执行效果相同)
```
A:select a.*,b.* from table1 a,table2 b where a.id=b.id
B:select * from table1 cross join table2 where table1.id=table2.id (注:cross join后加条件只能用where,不能用on)
```
三、交叉连接(完全)

1.概念:没有 WHERE 子句的交叉联接将产生联接所涉及的表的笛卡尔积。第一个表的行数乘以第二个表的行数等于笛卡尔积结果集的大小。(table1和table2交叉连接产生3*3=9条记录)

2.交叉连接:cross join (不带条件where...)

3.sql语句
```
select * from table1 cross join table2
-------------结果-------------
idnameidscore
------------------------------
1lee190
2zhang190
4wang190
1lee2100
2zhang2100
4wang2100
1lee370
2zhang370
4wang370
------------------------------
```
注释:返回3*3=9条记录,即笛卡尔积

4.等价(与下列执行效果相同)
```
A:select * from table1,table2
```

================================================
FILE: 09.MySQL篇/9.1.6 数据库范式.md
================================================
#### **题目**:数据库范式

#### **参考答案**:

1 第一范式(1NF)

在任何一个关系数据库中,第一范式(1NF)是对关系模式的基本要求,不满足第一范式(1NF)的数据库就不是关系数据库。
所谓第一范式(1NF)是指数据库表的每一列都是不可分割的基本数据项,同一列中不能有多个值,即实体中的某个属性不能有多个值或者不能有重复的属性。如果出现重复的属性,就可能需要定义一个新的实体,新的实体由重复的属性构成,新实体与原实体之间为一对多关系。在第一范式(1NF)中表的每一行只包含一个实例的信息。简而言之,第一范式就是无重复的列。

2 第二范式(2NF)

第二范式(2NF)是在第一范式(1NF)的基础上建立起来的,即满足第二范式(2NF)必须先满足第一范式(1NF)。第二范式(2NF)要求数据库表中的每个实例或行必须可以被惟一地区分。为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。这个惟一属性列被称为主关键字或主键、主码。
第二范式(2NF)要求实体的属性完全依赖于主关键字。所谓完全依赖是指不能存在仅依赖主关键字一部分的属性,如果存在,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体,新实体与原实体之间是一对多的关系。为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。简而言之,第二范式就是非主属性非部分依赖于主关键字。

3 第三范式(3NF)

满足第三范式(3NF)必须先满足第二范式(2NF)。简而言之,第三范式(3NF)要求一个数据库表中不包含已在其它表中已包含的非主关键字信息。例如,存在一个部门信息表,其中每个部门有部门编号(dept_id)、部门名称、部门简介等信息。那么在员工信息表中列出部门编号后就不能再将部门名称、部门简介等与部门有关的信息再加入员工信息表中。如果不存在部门信息表,则根据第三范式(3NF)也应该构建它,否则就会有大量的数据冗余。简而言之,第三范式就是属性不依赖于其它非主属性。(我的理解是消除冗余)

================================================
FILE: 09.MySQL篇/9.1.7 数据库优化的思路.md
================================================
#### **题目**:数据库优化的思路

#### **参考答案**:

这个我借鉴了慕课上关于数据库优化的课程。

1.SQL语句优化  

- 应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。  

- 应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:

    ```sql
    select id from t where num is null
    ```

    可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:  

    ```sql
    select id from t where num=0
    ```
    *liueleven* 的评论:

    ```
    不是非我杠精,关于null,isNull,isNotNull其实是要看成本的,是否回表等因素总和考虑,才会决定是要走索引还是走全表扫描。
    ```

    也给大家找了一个作者的博文([MySQL中IS NULL、IS NOT NULL、!=不能用索引?胡扯!](https://mp.weixin.qq.com/s/CEJFsDBizdl0SvugGX7UmQ)),仅供参考!!!

    [zhiyong0804d的意见]

    之所以未把第二条删除还是考虑可能很多人都被误导了。那这样的组织能让大家兼听则明。

- 很多时候用 exists 代替 in 是一个好的选择。

- 用Where子句替换HAVING 子句 因为HAVING 只会在检索出所有记录之后才对结果集进行过滤。

2.索引优化

    看上文索引

3.数据库结构优化

- 范式优化: 比如消除冗余(节省空间。。)

- 反范式优化:比如适当加冗余等(减少join)

- 拆分表:分区将数据在物理上分隔开,不同分区的数据可以制定保存在处于不同磁盘上的数据文件里。这样,当对这个表进行查询时,只需要在表分区中进行扫描,而不必进行全表扫描,明显缩短了查询时间,另外处于不同磁盘的分区也将对这个表的数据传输分散在不同的磁盘I/O,一个精心设置的分区可以将数据传输对磁盘I/O竞争均匀地分散开。对数据量大的时时表可采取此方法。可按月自动建表分区。

- 拆分其实又分垂直拆分和水平拆分:

  案例: 简单购物系统暂设涉及如下表:

    1.产品表(数据量10w,稳定) 

    2.订单表(数据量200w,且有增长趋势)

    3.用户表 (数据量100w,且有增长趋势)

  以mysql为例讲述下水平拆分和垂直拆分,mysql能容忍的数量级在百万静态数据可以到千万 
  
  **垂直拆分:**
  
  解决问题:表与表之间的io竞争 
  
  不解决问题:单表中数据量增长出现的压力 
  
  方案: 把产品表和用户表放到一个server上 订单表单独放到一个server上 
  
  **水平拆分:**
  
  解决问题:单表中数据量增长出现的压力 
  
  不解决问题:表与表之间的io争夺

  方案:**用户表** 通过性别拆分为男用户表和女用户表,**订单表** 通过已完成和完成中拆分为已完成订单和未完成订单,**产品表** 未完成订单放一个server上,已完成订单表盒男用户表放一个server上,女用户表放一个server上(女的爱购物 哈哈)。

4.服务器硬件优化

这个么多花钱咯!

================================================
FILE: 09.MySQL篇/9.1.8 存储过程与触发器的区别.md
================================================
#### **题目**:存储过程与触发器的区别

#### **参考答案**:

触发器与存储过程非常相似,触发器也是SQL语句集,两者唯一的区别是触发器不能用EXECUTE语句调用,而是在用户执行Transact-SQL语句时自动触发(激活)执行。

触发器是在一个修改了指定表中的数据时执行的存储过程。

通常通过创建触发器来强制实现不同表中的逻辑相关数据的引用完整性和一致性。由于用户不能绕过触发器,所以可以用它来强制实施复杂的业务规则,以确保数据的完整性。

触发器不同于存储过程,触发器主要是通过事件执行触发而被执行的,而存储过程可以通过存储过程名称名字而直接调用。当对某一表进行诸如UPDATE、INSERT、DELETE这些操作时,SQLSERVER就会自动执行触发器所定义的SQL语句,从而确保对数据的处理必须符合这些SQL语句所定义的规则。

================================================
FILE: 09.MySQL篇/9.1.9 解释 SQL 的 left join 和 right join.md
================================================
#### **题目**:解释 SQL 的 left join 和 right join

#### 出题人:阿里巴巴新零售技术质量部

#### **参考答案**:

left join 和 right join 都是两个表进行 merge 的操作,left join 是将右边的表 merge 到左边,right join 是将左边的表 merge 到右边,通常我们会指定按照哪几列进行 merge

举个例子:

**left table**

| 姓名 | 学号      |
| ---- | --------- |
| 小红 | SZ1716029 |
| 小明 | SZ1716030 |
| 小王 | SZ1716031 |

**right table**

| 学号      | 排名 |
| --------- | ---- |
| SZ1716029 | 1    |
| SZ1716030 | 2    |

**left table** left join **right table** on 学号

| 学号      | 姓名 | 排名 |
| --------- | ---- | ---- |
| SZ1716029 | 小红 | 1    |
| SZ1716030 | 小明 | 2    |
| SZ1716031 | 小王 | NULL |

**left table** right join **right table** on 学号

| 学号      | 姓名 | 排名 |
| --------- | ---- | ---- |
| SZ1716029 | 小红 | 1    |
| SZ1716030 | 小明 | 2    |



================================================
FILE: 10.Redis篇/.gitkeep
================================================


================================================
FILE: 10.Redis篇/10.1.0 使用Redis有哪些好处?.md
================================================
#### **题目**:

#### **参考答案**:

(1) 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)

(2) 支持丰富数据类型,支持string,list,set,sorted set,hash

(3) 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行

(4) 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除

================================================
FILE: 10.Redis篇/10.1.1 redis相比memcached有哪些优势?.md
================================================
#### **redis相比memcached有哪些优势?**:

#### **参考答案**:

(1) memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型

(2) redis的速度比memcached快很多

(3) redis可以持久化其数据

================================================
FILE: 10.Redis篇/10.1.2 redis常见性能问题和解决方案.md
================================================
#### **题目**:redis常见性能问题和解决方案

#### **参考答案**:

(1) Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件

(2) 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次

(3) 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内

(4) 尽量避免在压力很大的主库上增加从库

(5) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3...

这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。


================================================
FILE: 10.Redis篇/10.1.3 MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据.md
================================================
#### **题目**:MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据

#### **参考答案**:

相关知识:redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。redis 提供 6种数据淘汰策略:

voltile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰

volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰

volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰

allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰

allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰

no-enviction(驱逐):禁止驱逐数据



================================================
FILE: 10.Redis篇/10.1.3 zookeeper的四种类型的znode.md
================================================
#### **题目**:zookeeper的四种类型的znode

#### **参考答案**:

1、PERSISTENT-持久化目录节点 
客户端与zookeeper断开连接后,该节点依旧存在 
2、PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点
客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号 
3、EPHEMERAL-临时目录节点
客户端与zookeeper断开连接后,该节点被删除 
4、EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点
客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号

================================================
FILE: 10.Redis篇/10.1.4 Memcache与Redis的区别都有哪些?.md
================================================
#### **题目**:Memcache与Redis的区别都有哪些?

#### **参考答案**:

1)、存储方式

Memecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。

Redis有部份存在硬盘上,这样能保证数据的持久性。

2)、数据支持类型

Memcache对数据类型支持相对简单。

Redis有复杂的数据类型。

3)、使用底层模型不同

它们之间底层实现方式 以及与客户端之间通信的应用协议不一样。

Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。

4),value大小

redis最大可以达到1GB,而memcache只有1MB

================================================
FILE: 10.Redis篇/10.1.5 Redis 常见的性能问题都有哪些?如何解决?.md
================================================
#### **题目**:Redis 常见的性能问题都有哪些?如何解决?

#### **参考答案**:

1) Master写内存快照,save命令调度rdbSave函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以Master最好不要写内存快照。

2) Master AOF持久化,如果不重写AOF文件,这个持久化方式对性能的影响是最小的,但是AOF文件会不断增大,AOF文件过大会影响Master重启的恢复速度。Master最好不要做任何持久化工作,包括内存快照和AOF日志文件,特别是不要启用内存快照做持久化,如果数据比较关键,某个Slave开启AOF备份数据,策略为每秒同步一次。

3) Master调用BGREWRITEAOF重写AOF文件,AOF在重写的时候会占大量的CPU和内存资源,导致服务load过高,出现短暂服务暂停现象。

4) Redis主从复制的性能问题,为了主从复制的速度和连接的稳定性,Slave和Master最好在同一个局域网内

================================================
FILE: 10.Redis篇/10.1.6 redis最适合的场景.md
================================================
#### **题目**:redis 最适合的场景

#### **参考答案**:

Redis最适合所有数据in-memory的场景,虽然Redis也提供持久化功能,但实际更多的是一个disk-backed的功能,跟传统意义上的持久化有比较大的差别,那么可能大家就会有疑问,似乎Redis更像一个加强版的Memcached,那么何时使用Memcached,何时使用Redis呢?

如果简单地比较Redis与Memcached的区别,大多数都会得到以下观点:

1) Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
2) Redis支持数据的备份,即master-slave模式的数据备份。
3) Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用


================================================
FILE: 10.Redis篇/10.1.7 Redis的同步机制了解么?.md
================================================
#### **题目**:Redis的同步机制了解么?

#### **参考答案**:

主从同步。第一次同步时,主节点做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将rdb文件全量同步到复制节点,复制节点接受完成后将rdb镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。


================================================
FILE: 10.Redis篇/10.1.8 是否使用过Redis集群,集群的原理是什么?.md
================================================
#### **题目**:是否使用过Redis集群,集群的原理是什么?

#### **参考答案**:

Redis Sentinel着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。 

Redis Cluster着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储。


================================================
FILE: 11.MongoDB篇/.gitkeep
================================================


================================================
FILE: 11.MongoDB篇/11.1.0 什么是MongoDB?.md
================================================
#### **题目**:什么是MongoDB

#### **参考答案**:

MongoDB是一个文档数据库,提供好的性能,领先的非关系型数据库。采用BSON存储文档数据。2007年10月,MongoDB由10gen团队所发展。2009年2月首度推出。获得安装包和查看详细的API可以访问官网网址www.mongodb.com

================================================
FILE: 11.MongoDB篇/11.1.1 MongoDB是由哪种语言写的?.md
================================================
#### **题目**:MongoDB是由哪种语言写的

#### **参考答案**:
MongoDB用c++编写的,流行的开源数据库MySQL也是用C++开发的。C++1983年发行是一种使用广泛的计算机程序设计语言。它是一种通用程序设计语言,支持多重编程模式。

================================================
FILE: 11.MongoDB篇/11.1.2 MongoDB的优势有哪些?.md
================================================
#### **题目**:MongoDB的优势有哪些

#### **参考答案**:

面向文档的存储:文档存储以BSON格式(有大小限制,最大16M), 内置GridFS文件系统(一般存储大于16M的文件)。

 * 任何属性都可以建立索引。
 
 * 复制以及高可扩展性。
 
 * 自动分片。
 
 * 丰富的查询功能。
 
 * 快速的即时更新。
 
 *  来自 MongoDB 的专业支持。


================================================
FILE: 11.MongoDB篇/11.1.3 什么是数据库?.md
================================================
#### **题目**:什么是数据库

#### **参考答案**:

数据库可以看成是一个电子化的文件柜,用户可以对文件中的数据运行新增、检索、更新、删除等操作。数据库是一个所有集合的容器,在文件系统中每一个数据库都有一个相关的物理文件。

================================================
FILE: 11.MongoDB篇/11.1.4 什么是集合?.md
================================================
#### **题目**:什么是集合

#### **参考答案**:

集合就是一组 MongoDB 文档。它相当于关系型数据库(RDBMS)中的表这种概念。集合位于单独的一个数据库中。一个集合内的多个文档可以有多个不同的字段。一般来说,集合中的文档都有着相同或相关的目的。

================================================
FILE: 11.MongoDB篇/11.1.5 什么是文档?.md
================================================
#### **题目**:什么是文档

#### **参考答案**:

文档由一组key value组成。文档是动态模式,这意味着同一集合里的文档不需要有相同的字段和结构。在关系型数据库中table中的每一条记录相当于MongoDB中的一个文档。


================================================
FILE: 11.MongoDB篇/11.1.6 MongoDB和关系型数据库术语对比图.md
================================================


================================================
FILE: 11.MongoDB篇/11.1.7 什么是“mongod”?.md
================================================
#### **题目**:什么是”mongod“

#### **参考答案**:

mongod是处理MongoDB系统的主要进程。它处理数据请求,管理数据存储,和执行后台管理操作。当我们运行mongod命令意味着正在启动MongoDB进程,并且在后台运行。

================================================
FILE: 11.MongoDB篇/11.1.8 “mongod”参数有什么?.md
================================================
#### **题目**:"mongod"参数有什么

#### **参考答案**:

传递数据库存储路径,默认是"/data/db"
端口号 默认是 "27017"

================================================
FILE: 11.MongoDB篇/11.1.9 什么是“mongo”?.md
================================================
#### **题目**:什么是"mongo"

#### **参考答案**:

它是一个命令行工具用于连接一个特定的mongod实例。当我们没有带参数运行mongo命令它将使用默认的端口号和localhost连接。



================================================
FILE: 11.MongoDB篇/11.2.0 MongoDB哪个命令可以切换数据库?.md
================================================
#### **题目**:MongoDB哪个命令可以切换数据库

#### **参考答案**:

MongoDB 用use+数据库名称的方式来创建数据库。use会创建一个新的数据库,如果该数据库存在,则返回这个数据库。

>use database_name

================================================
FILE: 11.MongoDB篇/11.2.1 什么是非关系型数据库?.md
================================================
#### **题目**:什么是非关系型数据库

#### **参考答案**:

非关系型数据库是对不同于传统关系型数据库的统称。非关系型数据库的显著特点是不使用SQL作为查询语言,数据存储不需要特定的表格模式。由于简单的设计和非常好的性能所以被用于大数据和Web Apps等

================================================
FILE: 11.MongoDB篇/11.2.2 非关系型数据库有哪些类型?.md
================================================
#### **题目**:非关系型数据库有哪些类型

#### **参考答案**:

* Key-Value 存储 Eg:Amazon S3

* 图表 Eg:Neo4J

* 文档存储 Eg:MongoDB

 * 基于列存储 Eg:Cassandra

================================================
FILE: 11.MongoDB篇/11.2.3 为什么用MOngoDB?.md
================================================
#### **题目**:为什么用MOngoDB?

#### **参考答案**:

* 架构简单

* 没有复杂的连接

*  深度查询能力,MongoDB支持动态查询。

* 容易调试

* 容易扩展

* 不需要转化/映射应用对象到数据库对象

* 使用内部内存作为存储工作区,以便更快的存取数据。


================================================
FILE: 11.MongoDB篇/11.2.4 在哪些场景使用MongoDB?.md
================================================
#### **题目**:在哪些场景使用MongoDB

#### **参考答案**:

* 大数据

* 内容管理系统

* 移动端Apps

* 数据管理


================================================
FILE: 11.MongoDB篇/11.2.5 MongoDB中的命名空间是什么意思.md
================================================
#### **题目**:MongoDB中的命名空间是什么意思?

#### **参考答案**:

MongoDB内部有预分配空间的机制,每个预分配的文件都用0进行填充。

数据文件每新分配一次,它的大小都是上一个数据文件大小的2倍,每个数据文件最大2G。

MongoDB每个集合和每个索引都对应一个命名空间,这些命名空间的元数据集中在16M的*.ns文件中,平均每个命名占用约 628 字节,也即整个数据库的命名空间的上限约为24000。

如果每个集合有一个索引(比如默认的_id索引),那么最多可以创建12000个集合。如果索引数更多,则可创建的集合数就更少了。同时,如果集合数太多,一些操作也会变慢。

要建立更多的集合的话,MongoDB 也是支持的,只需要在启动时加上“--nssize”参数,这样对应数据库的命名空间文件就可以变得更大以便保存更多的命名。这个命名空间文件(.ns文件)最大可以为 2G。

每个命名空间对应的盘区不一定是连续的。与数据文件增长相同,每个命名空间对应的盘区大小都是随分配次数不断增长的。目的是为了平衡命名空间浪费的空间与保持一个命名空间数据的连续性。

需要注意的一个命名空间$freelist,这个命名空间用于记录不再使用的盘区(被删除的Collection或索引)。每当命名空间需要分配新盘区时,会先查看$freelist是否有大小合适的盘区可以使用,如果有就回收空闲的磁盘空间。

================================================
FILE: 11.MongoDB篇/11.2.6 哪些语言支持MongoDB.md
================================================
#### **题目** : 哪些语言支持MongoDB?

#### **参考答案**:

* C

* C++

* C#

* Java

* Node.js

* Perl

* Php 等


================================================
FILE: 11.MongoDB篇/11.2.7 在MongoDB中如何创建一个新的数据库?.md
================================================
#### **题目**:在MongoDB中如何创建一个新的数据库

#### **参考答案**:

MongoDB 用 use + 数据库名称 的方式来创建数据库。use 会创建一个新的数据库,如果该数据库存在,则返回这个数据库。

>use mydb
switched to db mydb



================================================
FILE: 11.MongoDB篇/11.2.8 在MongoDB中如何查看数据库列表?.md
================================================
#### **题目**:在MongoDB中如何查看数据库列表

#### **参考答案**:

使用命令"show dbs"

>show dbs

================================================
FILE: 11.MongoDB篇/11.2.9 MongoDB中的分片是什么意思?.md
================================================
#### **题目**:MongoDB中的分片是什么意思

#### **参考答案**:

分片是将数据水平切分到不同的物理节点。当应用数据越来越大的时候,数据量也会越来越大。当数据量增长时,单台机器有可能无法存储数据或可接受的读取写入吞吐量。利用分片技术可以添加更多的机器来应对数据量增加以及读写操作的要求。

参考:[https://docs.mongodb.com/manual/sharding/](https://docs.mongodb.com/manual/sharding/)

================================================
FILE: 11.MongoDB篇/11.3.0 如何查看使用MongoDB的连接?.md
================================================
如何查看使用MongoDB的连接Sharding - MongoDB Manual21.如何查看使用MongoDB的连接

使用命令"db.adminCommand(“connPoolStats”)"

>db.adminCommand(“connPoolStats”)

================================================
FILE: 11.MongoDB篇/11.3.1 什么是复制?.md
================================================


================================================
FILE: 11.MongoDB篇/11.3.2 在MongoDB中如何在集合中插入一个文档?.md
================================================


================================================
FILE: 11.MongoDB篇/11.3.3 在MongoDB中如何除去一个数据库?.md
================================================


================================================
FILE: 11.MongoDB篇/11.3.4 在MongoDB中如何创建一个集合?.md
================================================


================================================
FILE: 11.MongoDB篇/11.3.5 在MongoDB中如何查看一个已经创建的集合?.md
================================================


================================================
FILE: 11.MongoDB篇/11.3.6 在MongoDB中如何删除一个集合?.md
================================================


================================================
FILE: 11.MongoDB篇/11.3.7 为什么要在MongoDB中使用分析器?.md
================================================


================================================
FILE: 11.MongoDB篇/11.3.8 MongoDB支持主键外键关系吗?.md
================================================


================================================
FILE: 11.MongoDB篇/11.3.9 MongoDB支持哪些数据类型?.md
================================================
+ String 
+ Integer 
+ Double 
+ Boolean
+ Object
+ Object ID 
+ Arrays 
+ Min/Max Keys
+ Datetime
+ Code
+ Regular Expression等


================================================
FILE: 11.MongoDB篇/11.4.0 为什么要在MongoDB中用Code数据类型?.md
================================================


================================================
FILE: 11.MongoDB篇/11.4.1 为什么要在MongoDB中用Regular Expression数据类型?.md
================================================


================================================
FILE: 11.MongoDB篇/11.4.2 为什么在MongoDB中使用Object ID数据类型?.md
================================================
不使用自增是分布式维护起来非常困难。使用ObjectId可以保证不同机器都能用全局唯一的同种方法生成它并且确保不重复

================================================
FILE: 11.MongoDB篇/11.4.3 如何在集合中插入一个文档?.md
================================================


================================================
FILE: 11.MongoDB篇/11.4.4 “ObjectID”有哪些部分组成?.md
================================================
一共有四部分组成:时间戳、客户端ID、客户进程ID、三个字节的增量计数器


================================================
FILE: 11.MongoDB篇/11.4.5 在MongoDb中什么是索引?.md
================================================
索引是为了解决数据搜索效率低下引入的一种特殊的数据结构。索引存储在一个易于遍历读取的数据集合中,索引是对数据库表中一列或多列的值进行排序的一种结构。简单的说,索引就是将`文档`按照某个(或某些)字段顺序组织起来,以便能根据该字段高效的查询。



================================================
FILE: 11.MongoDB篇/11.4.6 如何添加索引?.md
================================================
MongoDB支持多种类型的索引,包括单字段索引、复合索引、多key索引、文本索引等,每种类型的索引有不同的使用场合。

按照类型可分为:

1. 单字段索引 

```db.person.createIndex( {age: 1} ) ```

对`person`集合建立对`age`的索引。

`{age: 1}` 代表升序索引,也可以通过`{age: -1}`来指定降序索引,对于单字段索引,升序/降序效果是一样的。

2. 复合索引 

``` db.person.createIndex( {age: 1, name: 1} ) ```

他是单字段索引的升级,可以对多个字段进行索引。按第一个字段排序,第一个字段相同的文档按第二个字段排序。

3. 多key索引

```
{"name" : "jack", "age" : 19, habbit: ["football, runnning"]}
db.person.createIndex( {habbit: 1} )  // 自动创建多key索引
db.person.find( {habbit: "football"} )
```

当索引的字段为数组时,创建出的索引称为多key索引,多key索引会为数组的每个元素建立一条索引,比如person表加入一个`habbit`字段(数组)用于描述兴趣爱好,需要查询有相同兴趣爱好的人就可以利用`habbit`字段的多key索引。

4. 其他索引



================================================
FILE: 11.MongoDB篇/11.4.7 MongoDB有哪些可替代产品?.md
================================================


================================================
FILE: 11.MongoDB篇/11.4.8 如何查询集合中的文档?.md
================================================


================================================
FILE: 11.MongoDB篇/11.4.9 用什么方法可以格式化输出结果?.md
================================================


================================================
FILE: 11.MongoDB篇/11.5.0 如何使用AND或OR条件循环查询集合中的文档?.md
================================================


================================================
FILE: 11.MongoDB篇/11.5.1 在MongoDB中如何更新数据?.md
================================================


================================================
FILE: 11.MongoDB篇/11.5.2 如何删除文档?.md
================================================


================================================
FILE: 11.MongoDB篇/11.5.3 在MongoDB中如何排序?.md
================================================


================================================
FILE: 11.MongoDB篇/11.5.4 什么是聚合?.md
================================================


================================================
FILE: 11.MongoDB篇/11.5.5 在MongoDB中什么是副本集?.md
================================================


================================================
FILE: 11.MongoDB篇/11.5.6 Mongodb存储特性与内部原理.md
================================================


================================================
FILE: 12.Zookeeper篇/.gitkeep
================================================


================================================
FILE: 12.Zookeeper篇/12.1.0 zookeeper是什么?.md
================================================
#### **参考答案**:

> A high-performance coordination service for distributed applications

Zookeeper是基于Google Chubby论文的开源实现,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、配置管理 等等。
由于Hadoop生态系统中很多项目都依赖于zookeeper,如Pig,Hive等, 似乎很像一个动物园管理员,于是取名为Zookeeper。


================================================
FILE: 12.Zookeeper篇/12.1.1 zookeeper提供了什么?.md
================================================
#### **题目**:zookeeper提供了什么?

#### **参考答案**:

1、文件系统
2、通知机制


================================================
FILE: 12.Zookeeper篇/12.1.2 zookeeper文件系统.md
================================================
#### **题目**:zookeeper文件系统

#### **参考答案**:

zookeeper提供一个类似unix文件系统目录的多层级节点命名空间(节点称为znode)。与文件系统不同的是,这些节点都可以设置关联的数据,而文件系统中只有文件节点可以存放数据而目录节点不行。zookeeper为了保证高吞吐和低延迟,在内存中维护了这个树状的目录结构,这种特性使得zookeeper不能用于存放大量的数据,每个节点的存放数据上限为1M。

================================================
FILE: 12.Zookeeper篇/12.1.3 zookeeper的四种类型的znode.md
================================================
#### 题目:zookeeper的四种类型的znode

#### 参考答案:

PERSISTENT                持久化节点
    
PERSISTENT_SEQUENTIAL     顺序自动编号持久化节点,这种节点会根据当前已存在的节点数自动加 1
    
EPHEMERAL                 临时节点, 客户端session超时这类节点就会被自动删除
   
EPHEMERAL_SEQUENTIAL      临时自动编号节点

================================================
FILE: 12.Zookeeper篇/12.1.4 zookeeper通知机制.md
================================================
#### **题目**:zookeeper通知机制

#### **参考答案**:

client端会对某个znode建立一个watcher事件,当该znode发生变化时,zk会主动通知watch这个znode的client,然后client根据znode的变化来做出业务上的改变等。

#### watcher的特点:
- 轻量级:一个callback函数。
- 异步性:不会block正常的读写请求。
- 主动推送:Watch被触发时,由Zookeeper服务端主动将更新推送给客户端。
- 一次性:数据变化时,Watch只会被触发一次。如果客户端想得到后续更新的通知,必须要在 Watch 被触发后重新注册一个 Watch。
- 仅通知:仅通知变更类型,不附带变更后的结果。
- 顺序性:如果多个更新触发了多个Watch,那 Watch 被触发的顺序与更新顺序一致。

#### 使用watch的注意事项:
- 由于watcher是一次性的,所以需要自己去实现永久watch
- 如果被watch的节点频繁更新,会出现“丢数据”的情况
- watcher数量过多会导致性能下降


================================================
FILE: 12.Zookeeper篇/12.1.5 zookeeper有哪些应用场景?.md
================================================
#### **题目**:zookeeper有哪些应用场景

#### **参考答案**:

1、名字服务

2、配置管理

3、集群管理

4、分布式锁

5、队列管理

6、消息订阅


================================================
FILE: 12.Zookeeper篇/12.1.6 zk的命名服务.md
================================================
#### **题目**: zk的命名服务

#### **参考答案**:

命名服务是指通过指定的名字来获取资源或者服务的地址,利用zk创建一个全局的路径,即是唯一的路径,这个路径就可以作为一个名字,指向集群中的集群,提供的服务的地址,或者一个远程的对象等等。

================================================
FILE: 12.Zookeeper篇/12.1.7 zk的配置管理服务.md
================================================
#### **题目**:zk的配置管理

#### **参考答案**:

程序分布式的部署在不同的机器上,将程序的配置信息放在zk的znode下,当有配置发生改变时,也就是znode发生变化时,可以通过改变zk中某个目录节点的内容,利用watcher通知给各个客户端,从而更改配置。

================================================
FILE: 12.Zookeeper篇/12.1.8 zk的集群管理.md
================================================
#### **题目**:zookeeper集群管理

#### **参考答案**:

所谓集群管理无在乎两点:是否有机器退出和加入、选举master。 
对于第一点,所有机器约定在父目录下创建临时目录节点,然后监听父目录节点的子节点变化消息。一旦有机器挂掉,该机器与 zookeeper的连接断开,其所创建的临时目录节点被删除,所有其他机器都收到通知:某个兄弟目录被删除,于是,所有人都知道:它上船了。
新机器加入也是类似,所有机器收到通知:新兄弟目录加入,highcount又有了,对于第二点,我们稍微改变一下,所有机器创建临时顺序编号目录节点,每次选取编号最小的机器作为master就好。

================================================
FILE: 12.Zookeeper篇/12.1.9 zk的分布式锁.md
================================================
#### **题目**:zookeeper分布式锁

#### **参考答案**:

有了zookeeper的一致性文件系统,锁的问题变得容易。锁服务可以分为两类,一个是保持独占,另一个是控制时序。 
对于第一类,我们将zookeeper上的一个znode看作是一把锁,通过createznode的方式来实现。所有客户端都去创建 /distribute_lock 节点,最终成功创建的那个客户端也即拥有了这把锁。用完删除掉自己创建的distribute_lock 节点就释放出锁。 
对于第二类, /distribute_lock 已经预先存在,所有客户端在它下面创建临时顺序编号目录节点,和选master一样,编号最小的获得锁,用完删除,依次方便。

**获取分布式锁的流程**

<img src = "zk_distributed_locker1.png" />

在获取分布式锁的时候在locker节点下创建临时顺序节点,释放锁的时候删除该临时节点。客户端调用createNode方法在locker下创建临时顺序节点,
然后调用getChildren(“locker”)来获取locker下面的所有子节点,注意此时不用设置任何Watcher。客户端获取到所有的子节点path之后,如果发现自己创建的节点在所有创建的子节点序号最小,那么就认为该客户端获取到了锁。如果发现自己创建的节点并非locker所有子节点中最小的,说明自己还没有获取到锁,此时客户端需要找到比自己小的那个节点,然后对其调用exist()方法,同时对其注册事件监听器。之后,让这个被关注的节点删除,则客户端的Watcher会收到相应通知,此时再次判断自己创建的节点是否是locker子节点中序号最小的,如果是则获取到了锁,如果不是则重复以上步骤继续获取到比自己小的一个节点并注册监听。当前这个过程中还需要许多的逻辑判断。

<img src = "zk_distributed_locker2.png" />

代码的实现主要是基于互斥锁,获取分布式锁的重点逻辑在于BaseDistributedLock,实现了基于Zookeeper实现分布式锁的细节。


================================================
FILE: 12.Zookeeper篇/12.2.0 zk队列管理.md
================================================
#### **题目**:zk队列管理

#### **参考答案**:

两种类型的队列:
1、同步队列,当一个队列的成员都聚齐时,这个队列才可用,否则一直等待所有成员到达。 
2、队列按照 FIFO 方式进行入队和出队操作。 
第一类,在约定目录下创建临时目录节点,监听节点数目是否是我们要求的数目。 
第二类,和分布式锁服务中的控制时序场景基本原理一致,入列有编号,出列按编号。在特定的目录下创建PERSISTENT_SEQUENTIAL节点,创建成功时Watcher通知等待的队列,队列删除序列号最小的节点用以消费。此场景下Zookeeper的znode用于消息存储,znode存储的数据就是消息队列中的消息内容,SEQUENTIAL序列号就是消息的编号,按序取出即可。由于创建的节点是持久化的,所以不必担心队列消息的丢失问题。

================================================
FILE: 12.Zookeeper篇/12.2.1 zk数据复制.md
================================================
#### **题目**:zk数据复制

#### **参考答案**:

Zookeeper作为一个集群提供一致的数据服务,自然,它要在所有机器间做数据复制。
<br/>
数据复制的好处: 
<br/>
1、容错:一个节点出错,不致于让整个系统停止工作,别的节点可以接管它的工作; 
<br/>
2、提高系统的扩展能力 :把负载分布到多个节点上,或者增加节点来提高系统的负载能力; 
<br/>
3、提高性能:让客户端本地访问就近的节点,提高用户访问速度。
<br/>
<br/>

从客户端读写访问的透明度来看,数据复制集群系统分下面两种: 
<br/>
1、写主(WriteMaster) :对数据的修改提交给指定的节点。读无此限制,可以读取任何一个节点。这种情况下客户端需要对读与写进行区别,俗称读写分离; 
<br/>
2、写任意(Write Any):对数据的修改可提交给任意的节点,跟读一样。这种情况下,客户端对集群节点的角色与变化透明。
<br/>

对zookeeper来说,它采用的方式是写任意。通过增加机器,它的读吞吐能力和响应能力扩展性非常好,而写,随着机器的增多吞吐能力肯定下降(这也是它建立observer的原因),而响应能力则取决于具体实现方式,是延迟复制保持最终一致性,还是立即复制快速响应。


================================================
FILE: 12.Zookeeper篇/12.2.2 zk中zab的工作原理.md
================================================
#### **题目**:zk中zab的工作原理

#### **参考答案**:

ZAB 是 ZooKeeper Atomic Broadcast (ZooKeeper 原子广播协议)的缩写,它是特别为 ZooKeeper 设计的崩溃可恢复的原子消息广播算法。ZooKeeper 使用 Leader来接收并处理所有事务请求,并采用 ZAB 协议,将服务器数据的状态变更以事务 Proposal 的形式广播到所有的 Follower 服务器上去。这种主备模型架构保证了同一时刻集群中只有一个服务器广播服务器的状态变更,因此能够很好的保证事物的完整性和顺序性。

Zab协议有两种模式,它们分别是恢复模式(recovery)和广播模式(broadcast)。当服务启动或者在leader崩溃后,Zab就进入了恢复模式,当leader被选举出来,且大多数follower完成了和leader的状态同步以后, 恢复模式就结束了,ZAB开始进入广播模式。


================================================
FILE: 12.Zookeeper篇/12.2.3 zk是如何保证事务的顺序一致性.md
================================================
#### **题目**:zookeeper是如何保证事务的顺序一致性的?

#### **参考答案**:

zookeeper采用了递增的事务Id来标识,所有的proposal(提议)都在被提出的时候加上了zxid,zxid实际上是一个64位的数字,高32位是epoch(时期; 纪元; 世; 新时代)用来标识leader是否发生改变,如果有新的leader产生出来,epoch会自增,低32位用来递增计数。当新产生proposal的时候,会依据数据库的两阶段过程,首先会向其他的server发出事务执行请求,如果超过半数的机器都能执行并且能够成功,那么就会开始执行。


================================================
FILE: 12.Zookeeper篇/12.2.4 zk集群下server工作状态.md
================================================
#### **题目**:zk集群下server工作状态

#### **参考答案**:

每个Server在工作过程中有四种状态: 

LOOKING:当前Server不知道leader是谁,正在搜寻

LEADING:当前server角色为leader

FOLLOWING:当前server角色为follower

OBSERVING:当前server角色为observer


================================================
FILE: 12.Zookeeper篇/12.2.5 zk是如何选举Leader的?.md
================================================
#### **题目**:zookeeper是如何选举Leader的?

#### **参考答案**:

当leader崩溃或者leader失去大多数的follower,这时zk进入恢复模式,恢复模式需要重新选举出一个新的leader,让所有的Server都恢复到一个正确的状态。Zk的选举算法有两种:一种是基于basic paxos实现的,另外一种是基于fast paxos算法实现的。系统默认的选举算法为fast paxos。

1、Zookeeper选主流程(basic paxos)
(1)选举线程由当前Server发起选举的线程担任,其主要功能是对投票结果进行统计,并选出推荐的Server; 
(2)选举线程首先向所有Server发起一次询问(包括自己); 
(3)选举线程收到回复后,验证是否是自己发起的询问(验证zxid是否一致),然后获取对方的id(myid),并存储到当前询问对象列表中,最后获取对方提议的leader相关信息(id,zxid),并将这些信息存储到当次选举的投票记录表中; 
(4)收到所有Server回复以后,就计算出zxid最大的那个Server,并将这个Server相关信息设置成下一次要投票的Server; 
(5)线程将当前zxid最大的Server设置为当前Server要推荐的Leader,如果此时获胜的Server获得n/2 + 1的Server票数,设置当前推荐的leader为获胜的Server,将根据获胜的Server相关信息设置自己的状态,否则,继续这个过程,直到leader被选举出来。 通过流程分析我们可以得出:要使Leader获得多数Server的支持,则Server总数必须是奇数2n+1,且存活的Server的数目不得少于n+1. 每个Server启动后都会重复以上流程。在恢复模式下,如果是刚从崩溃状态恢复的或者刚启动的server还会从磁盘快照中恢复数据和会话信息,zk会记录事务日志并定期进行快照,方便在恢复时进行状态恢复。

<img src="zk_zab_basic_paxos.png" />

2、Zookeeper选主流程(fast paxos)
fast paxos流程是在选举过程中,某Server首先向所有Server提议自己要成为leader,当其它Server收到提议以后,解决epoch和 zxid的冲突,并接受对方的提议,然后向对方发送接受提议完成的消息,重复这个流程,最后一定能选举出Leader。

<img src="zk_zab_fast_paxos.png" />


================================================
FILE: 12.Zookeeper篇/12.2.6 zk同步流程.md
================================================
#### **题目**:zookeeper同步流程

#### **参考答案**:

选完Leader以后,zk就进入状态同步过程。 

1. Leader等待Follower和Observer连接;

2. Follower连接leader,将最大的zxid发送给leader;

3. Leader根据follower的zxid确定同步点;

4. 完成同步后通知follower 已经成为uptodate状态;

5. Follower收到uptodate消息后,又可以重新接受client的请求进行服务了。

<img src="zk_sync.png" />


数据同步的4种方式:

1、SNAP-全量同步
  - 条件:peerLastZxid<minCommittedLog
  - 说明:证明二者数据差异太大,follower数据过于陈旧,leader发送快照SNAP指令给follower全量同步数据,即leader将所有数据全量同步到follower

2、DIFF-增量同步
  - 条件:minCommittedLog<=peerLastZxid<=maxCommittedLog
  - 说明:证明二者数据差异不大,follower上有一些leader上已经提交的提议proposal未同步,此时需要增量提交这些提议即可

3、TRUNC-仅回滚同步
  - 条件:peerLastZxid>maxCommittedLog
  - 说明:证明follower上有些提议proposal并未在leader上提交,follower需要回滚到zxid为maxCommittedLog对应的事务操作

4、TRUNC+DIFF-回滚+增量同步
  - 条件:minCommittedLog<=peerLastZxid<=maxCommittedLog
  - 说明:leader a已经将事务truncA提交到本地事务日志中,但没有成功发起proposal协议进行投票就宕机了;然后集群中剔除原leader a重新选举出新leader b,又提交了若干新的提议proposal,然后原leader a重新服务又加入到集群中说明:此时a,b都有一些对方未提交的事务,若b是leader, a需要先回滚truncA然后增量同步新leader b上的数据。


================================================
FILE: 12.Zookeeper篇/12.2.7 分布式通知和协调.md
================================================
#### **题目**:分布式通知和协调

#### **参考答案**:

对于系统调度来说:操作人员发送通知实际是通过控制台改变某个节点的状态,然后zk将这些变化发送给注册了这个节点的watcher的所有客户端。

对于执行情况汇报:每个工作进程都在某个目录下创建一个临时节点。并携带工作的进度数据,这样汇总的进程可以监控目录子节点的变化获得工作进度的实时的全局情况。

================================================
FILE: 12.Zookeeper篇/12.2.8 zk的session机制.md
================================================
#### **题目**:zk的session机制

#### **参考答案**:

zookeeper会为每个客户端分配一个session,类似于web服务器一样,用来标识客户端的身份。

session的作用:


- 客户端标识
- 超时检查
- 请求的顺序执行
- 维护临时节点的生命周期
- watcher通知

session的状态:

- CONNECTING
- CONNECTED
- RECONNECTING
- RECONNECTED
- CLOSED

session的属性:

- SessionID:会话ID,全局唯一
- TimeOut:会话超时时间
- TickTime:下次会话超时时间点
- isClosing:会话是否已经被关闭

sessionID的构成:

- 高8位代表创建Session时所在的zk节点的id
- 中间40位代表zk节点当前角色在创建的时候的时间戳
- 低16位是一个计数器,初始值为0


================================================
FILE: 13.Nginx篇/.gitkeep
================================================


================================================
FILE: 14.算法篇/.gitkeep
================================================


================================================
FILE: 15.内存篇/.gitkeep
================================================


================================================
FILE: 16.CPU篇/.gitkeep
================================================


================================================
FILE: 17.磁盘篇/.gitkeep
================================================


================================================
FILE: 18.网络通信篇/.gitkeep
================================================


================================================
FILE: 19.安全篇/.gitkeep
================================================


================================================
FILE: 20.并发篇/.gitkeep
================================================


================================================
FILE: 2023adding.md
================================================

# C/C++ 常见1000道面试题

## 1.变量的声明和定义有什么区别

为变量分配地址和存储空间的称为定义,不分配地址的称为声明。一个变量可以在多个地方声明,
但是只在一个地方定义。加入 extern 修饰的是变量的声明,说明此变量将在文件以外或在文件后面部分定义。说明:很多时候一个变量,只是声明不分配内存空间,直到具体使用时才初始化,分配内存空间,
如外部变量。

## 2.写出 bool 、int、 float、指针变量与“零值”比较的 if 语句

```
//bool 型数据: 
if( flag ) 
{ 
 	A; 
} 
else 
{ 
 	B; 
} 
//int 型数据: 
if( 0 != flag ) 
{ 
 	A; 
} 
else { 
 	B; 
} 
//指针型: 
if( NULL == flag ) 
{ 
 	A; 
} 
else { 
 	B; 
} 
//float 型数据: 
if ( ( flag >= -NORM ) && ( flag <= NORM ) ) 
{ 
 	A; 
} 
```


注意:应特别注意在 int、指针型变量和“零值”比较的时候,把“零值”放在左边,这样当把“==” 误写成“=”时,编译器可以报错,否则这种逻辑错误不容易发现,并且可能导致很严重的后果。

## 3.sizeof 和 strlen 的区别

sizeof 和 strlen 有以下区别:
1 sizeof 是一个操作符,strlen 是库函数。
2 sizeof 的参数可以是数据的类型,也可以是变量,而 strlen 只能以结尾为‘\0‘的字符串作参数。
3 编译器在编译时就计算出了 sizeof 的结果。而 strlen 函数必须在运行时才能计算出来。并且 sizeof 计算的是数据类型占内存的大小,而 strlen 计算的是字符串实际的长度。
4 数组做 sizeof 的参数不退化,传递给 strlen 就退化为指针了。
注意:有些是操作符看起来像是函数,而有些函数名看起来又像操作符,这类容易混淆的名称一定要加以区分,否则遇到数组名这类特殊数据类型作参数时就很容易出错。最容易混淆为函数的操作符就是 sizeof。

## 4.C 语言的关键字 static 和 C++ 的关键字 static 有什么区别

在 C 中 static 用来修饰局部静态变量和外部静态变量、函数。而 C++中除了上述功能外,还用来定义类的成员变量和函数。即静态成员和静态成员函数。
注意:编程时 static 的记忆性,和全局性的特点可以让在不同时期调用的函数进行通信,传递信息,而 C++的静态成员则可以在多个对象实例间进行通信,传递信息。

## 5.C中的 malloc 和C++中的 new 有什么区别

malloc 和 new 有以下不同:
(1) new、delete 是操作符,可以重载,只能在 C++中使用。
(2) malloc、free 是函数,可以覆盖,C、C++中都可以使用。
(3) new 可以调用对象的构造函数,对应的 delete 调用相应的析构函数。
(4) malloc 仅仅分配内存,free 仅仅回收内存,并不执行构造和析构函数
(5) new、delete 返回的是某种数据类型指针,malloc、free 返回的是 void 指针。
注意:malloc 申请的内存空间要用 free 释放,而 new 申请的内存空间要用 delete 释放,不要混用。
因为两者实现的机理不同。

## 6.写一个“标准”宏 MIN

```
#define min(a,b)((a)<=(b)?(a):(b)) 
```

注意:在调用时一定要注意这个宏定义的副作用,如下调用:

```
((++*p)<=(x)?(++*p):(x) 
```

p 指针就自加了两次,违背了 MIN 的本意。

## 7.一个指针可以是 volatile 吗

可以,因为指针和普通变量一样,有时也有变化程序的不可控性。常见例:子中断服务子程序修改一个指向一个 buffer 的指针时,必须用 volatile 来修饰这个指针。
说明:指针是一种普通的变量,从访问上没有什么不同于其他变量的特性。其保存的数值是个整型数据,和整型变量不同的是,这个整型数据指向的是一段内存地址。

## 8.a 和&a 有什么区别

请写出以下代码的打印结果,主要目的是考察 a 和&a 的区别。

```
#include<stdio.h> 
void main( void ) 
{ 
 	int a[5]={1,2,3,4,5}; 
 	int *ptr=(int *)(&a+1); 
 	printf("%d,%d",*(a+1),*(ptr-1));  	
 	return; 
} 
```

输出结果:2,5。
注意:数组名 a 可以作数组的首地址,而&a 是数组的指针。思考,将原式的 int *ptr=(int *)(&a+1); 改为 int *ptr=(int *)(a+1);时输出结果将是什么呢?

## 9.简述 C、C++程序编译的内存分配情况

C、C++中内存分配方式可以分为三种:
(1) 从静态存储区域分配:
内存在程序编译时就已经分配好,这块内存在程序的整个运行期间都存在。速度快、不容易出错,因为有系统会善后。例如全局变量,static 变量等。
(2) 在栈上分配:
在执行函数时,函数内局部变量的存储单元都在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
(3) 从堆上分配:
即动态内存分配。程序在运行的时候用 malloc 或 new 申请任意大小的内存,程序员自己负责在何时用 free 或 delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活。如果在堆上分配了空间,就有责任回收它,否则运行的程序会出现内存泄漏,另外频繁地分配和释放不同大小的堆空间将会产生堆内碎块。
一个 C、C++程序编译时内存分为 5 大存储区:堆区、栈区、全局区、文字常量区、程序代码区。

## 10.简述 strcpy、sprintf 与 memcpy 的区别

三者主要有以下不同之处:
(1) 操作对象不同,strcpy 的两个操作对象均为字符串,sprintf 的操作源对象可以是多种数据类型,目的操作对象是字符串,memcpy 的两个对象就是两个任意可操作的内存地址,并不限于何种数据类型。
(2) 执行效率不同,memcpy 最高,strcpy 次之,sprintf 的效率最低。
(3) 实现功能不同,strcpy 主要实现字符串变量间的拷贝,sprintf 主要实现其他数据类型格式到字符串的转化,memcpy 主要是内存块间的拷贝。
说明:strcpy、sprintf 与 memcpy 都可以实现拷贝的功能,但是针对的对象不同,根据实际需求,来选择合适的函数实现拷贝功能。

## 11.设置地址为 0x67a9 的整型变量的值为 0xaa66

```
int *ptr;  
ptr = (int *)0x67a9;  
*ptr = 0xaa66;  
```


说明:这道题就是强制类型转换的典型例子,无论在什么平台地址长度和整型数据的长度是一样的,即一个整型数据可以强制转换成地址指针类型,只要有意义即可。

## 12.面向对象的三大特征

面向对象的三大特征是封装性、继承性和多态性:
封装性:将客观事物抽象成类,每个类对自身的数据和方法实行 protection(private, protected, public)。
继承性:广义的继承有三种实现形式:实现继承(使用基类的属性和方法而无需额外编码的能力)、可视继承(子窗体使用父窗体的外观和实现代码)、接口继承(仅使用属性和方法,实现滞后到子类实现)。
多态性:是将父类对象设置成为和一个或更多它的子对象相等的技术。用子类对象给父类对象赋值之后,父类对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。 这部分需要熟悉掌握原理虚函数,了解一些概念(静态多态、动态多态)等,面试时经常会问。
说明:面向对象的三个特征是实现面向对象技术的关键,每一个特征的相关技术都非常的复杂,程序员应该多看、多练。

## 13.C++的空类有哪些成员函数

缺省构造函数。
缺省拷贝构造函数。
缺省析构函数。
缺省赋值运算符。
缺省取址运算符。
缺省取址运算符 const。
注意:有些书上只是简单的介绍了前四个函数。没有提及后面这两个函数。但后面这两个函数也是空类的默认函数。另外需要注意的是,只有当实际使用这些函数的时候,编译器才会去定义它们。

## 14.谈谈你对拷贝构造函数和赋值运算符的认识

拷贝构造函数和赋值运算符重载有以下两个不同之处:
(1) 拷贝构造函数生成新的类对象,而赋值运算符不能。
(2) 由于拷贝构造函数是直接构造一个新的类对象,所以在初始化这个对象之前不用检验源对象是否和新建对象相同。而赋值运算符则需要这个操作,另外赋值运算中如果原来的对象中有内存分配要先把内存释放掉
注意:当有类中有指针类型的成员变量时,一定要重写拷贝构造函数和赋值运算符,不要使用默认的。

## 15.用 C++设计一个不能被继承的类

```
template <typename T> class A 
{ 
 	friend T; private: 
 	A() {} 
 	~A() {} 
}; 

class B : virtual public A<B> 
{ 
public: 
 	B() {} 
 	~B() {} 
}; 
class C : virtual public B 
{ 
public: 
 	C() {} 
 	~C() {} 
}; 
void main( void ) 
{ 
 	B b;  //C c; 
 	return; 
} 
```


注意:构造函数是继承实现的关键,每次子类对象构造时,首先调用的是父类的构造函数,然后才是自己的。

注意:构造函数是继承实现的关键,每次子类对象构造时,首先调用的是父类的构造函数,然后才是自己的。

## 16.访问基类的私有虚函数

写出以下程序的输出结果:

```
#include <iostream.h> 
class A 
{ 
 	virtual void g() 
 	{ 
 	 	cout << "A::g" << endl; 
 	} 
private: 
 	virtual void f() 
 	{ 
 	 	cout << "A::f" << endl; 
 	} 
}; 
class B : public A 
{ 
 	void g() 
 	{ 
 	 	cout << "B::g" << endl; 
 	} 
 	virtual void h() 
 	{ 
 	 	cout << "B::h" << endl; 
 	} 
}; 
typedef void( *Fun )( void ); void main() 
{ 
 	B b; 
 	Fun pFun; 
 	for(int i = 0 ; i < 3; i++) 
 	{ 
 	 	pFun = ( Fun )*( ( int* ) * ( int* )( &b ) + i ); 
 	 	pFun(); 
 	} 	 
} 
```

输出结果:
B::g
A::f
B::h
注意:本题主要考察了面试者对虚函数的理解程度。一个对虚函数不了解的人很难正确的做出本题。
在学习面向对象的多态性时一定要深刻理解虚函数表的工作原理。

## 17.简述类成员函数的重写、重载和隐藏的区别

(1)重写和重载主要有以下几点不同。
范围的区别:被重写的和重写的函数在两个类中,而重载和被重载的函数在同一个类中。
参数的区别:被重写函数和重写函数的参数列表一定相同,而被重载函数和重载函数的参数列表一定不同。
virtual 的区别:重写的基类中被重写的函数必须要有 virtual 修饰,而重载函数和被重载函数可以被
virtual 修饰,也可以没有。
(2)隐藏和重写、重载有以下几点不同。
与重载的范围不同:和重写一样,隐藏函数和被隐藏函数不在同一个类中。
参数的区别:隐藏函数和被隐藏的函数的参数列表可以相同,也可不同,但是函数名肯定要相同。当参数不相同时,无论基类中的参数是否被 virtual 修饰,基类的函数都是被隐藏,而不是被重写。
说明:虽然重载和覆盖都是实现多态的基础,但是两者实现的技术完全不相同,达到的目的也是完全不同的,覆盖是动态态绑定的多态,而重载是静态绑定的多态。

## 18.简述多态实现的原理

编译器发现一个类中有虚函数,便会立即为此类生成虚函数表 vtable。虚函数表的各表项为指向对应虚函数的指针。编译器还会在此类中隐含插入一个指针 vptr(对 vc 编译器来说,它插在类的第一个位置上)指向虚函数表。调用此类的构造函数时,在类的构造函数中,编译器会隐含执行 vptr 与 vtable 的关联代码,将 vptr 指向对应的 vtable,将类与此类的 vtable 联系了起来。另外在调用类的构造函数时,指向基础类的指针此时已经变成指向具体的类的 this 指针,这样依靠此 this 指针即可得到正确的 vtable,。
如此才能真正与函数体进行连接,这就是动态联编,实现多态的基本原理。
注意:一定要区分虚函数,纯虚函数、虚拟继承的关系和区别。牢记虚函数实现原理,因为多态
C++面试的重要考点之一,而虚函数是实现多态的基础。

## 19.链表和数组有什么区别

数组和链表有以下几点不同:
(1) 存储形式:数组是一块连续的空间,声明时就要确定长度。链表是一块可不连续的动态空间,长度可变,每个结点要保存相邻结点指针。
(2) 数据查找:数组的线性查找速度快,查找操作直接使用偏移地址。链表需要按顺序检索结点,效率低。
(3) 数据插入或删除:链表可以快速插入和删除结点,而数组则可能需要大量数据移动。
(4) 越界问题:链表不存在越界问题,数组有越界问题。
说明:在选择数组或链表数据结构时,一定要根据实际需要进行选择。数组便于查询,链表便于插入删除。数组节省空间但是长度固定,链表虽然变长但是占了更多的存储空间。

## 20.怎样把一个单链表反序

(1) 反转一个链表。循环算法。

```
List reverse(List   n)  
{  
 	if(!n)  	 	 	 	 	 	//判断链表是否为空,为空即退出。 
 	{ 
 	 	return n; 
 	} 
 	list cur = n.next; 	 	 	 	//保存头结点的下个结点  
 	list pre = n; 	 	 
 	list tmp; 	 	 	//保存头结点 
 	pre.next = null;  	 	 	 	//头结点的指针指空,转换后变尾结点 
 	while ( NULL != cur.next )  	 	//循环直到 cur.next 为空 
 	{ 
 		tmp = cur;  	 	 	 	
	} 	 	
	tmp.next = pre;    	
	pre = tmp; 
 	cur = cur.next; 
	

return tmp; 	 	 	 	 	//f 返回头指针 

}
```

(2) 反转一个链表。递归算法。

```
List *reverse( List *oldList, List *newHead = NULL )  
{ 
 	List *next = oldList-> next;  	 	//记录上次翻转后的链表  	
 	oldList-> next = newHead;  	 	//将当前结点插入到翻转后链表的开头  	
 	newHead = oldList;   	 	 	//递归处理剩余的链表 
 	return ( next==NULL )? newHead: reverse( t, newHead );  
} 
```


说明:循环算法就是移动过程,比较好理解和想到。递归算法的设计虽有一点难度,但是理解了循环算法,再设计递归算法就简单多了。

## 21.简述队列和栈的异同

队列和栈都是线性存储结构,但是两者的插入和删除数据的操作不同,队列是“先进先出”,栈是
“后进先出”。
注意:区别栈区和堆区。堆区的存取是“顺序随意”,而栈区是“后进先出”。栈由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。堆一般由程序员分配释放, 若程序员不释放,程序结束时可能由 OS 回收。分配方式类似于链表。
它与本题中的堆和栈是两回事。堆栈只是一种数据结构,而堆区和栈区是程序的不同内存存储区域。

## 22.能否用两个栈实现一个队列的功能

```
//结点结构体:
typedef struct node
{
   int data;
   node *next;
}node, *LinkStack;
//创建空栈:
LinkStack CreateNULLStack(LinkStack &S)
{
   S = (LinkStack)malloc(sizeof(node)); //申请新结点
   if (NULL == S)
   {
   	printf("Fail to malloc a new node.\n")
   		return NULL;
   }
   S->data = 0; //初始化新结点
   S->next = NULL;
   return S;
}
//栈的插入函数:
LinkStack Push(LinkStack &S, int data)
{
   if (NULL == S) //检验栈
   {
   	printf("There no node in stack!");
   	return NULL;
   }
   LinkStack p = NULL;
   p = (LinkStack)malloc(sizeof(node)); //申请新结点
   if (NULL == p)
   {
   	printf("Fail to malloc a new node.\n");
   	return S;
   }
   if (NULL == S->next)
   {
   	p->next = NULL;
   }
   else
   {
   	p->next = S->next;
   }
   p->data = data; //初始化新结点
   S->next = p; //插入新结点
   return S;
}
//出栈函数:
node Pop(LinkStack &S)
{
   node temp;
   temp.data = 0;
   temp.next = NULL;
   if (NULL == S) //检验栈
   {
   	printf("There no node in stack!");
   	return temp;
   }
   temp = *S;
   10
   	if (S->next == NULL)
   	{
   		printf("The stack is NULL,can't pop!\n");
   		return temp;
   	}
   LinkStack p = S->next; //节点出栈
   S->next = S->next->next;
   temp = *p;
   free(p);
   p = NULL;
   return temp;
}
//双栈实现队列的入队函数:
LinkStack StackToQueuPush(LinkStack &S, int data)
{
   node n;
   LinkStack S1 = NULL;
   CreateNULLStack(S1); //创建空栈
   while (NULL != S->next) //S 出栈入 S1
   {
   	n = Pop(S);
   	Push(S1, n.data);
   }
   Push(S1, data); //新结点入栈
   while (NULL != S1->next) //S1 出栈入 S
   {
   	n = Pop(S1);
   	Push(S, n.data);
   }
}
```


说明:用两个栈能够实现一个队列的功能,那用两个队列能否实现一个队列的功能呢?结果是否定的,因为栈是先进后出,将两个栈连在一起,就是先进先出。而队列是现先进先出,无论多少个连在一起都是先进先出,而无法实现先进后出。

## 23.计算一颗二叉树的深度

深度的计算函数:

```
int depth(BiTree T) 
{ 
	if(!T) return 0;  	 	 	 	 	//判断当前结点是否为叶子结点 
	int d1= depth(T->lchild);  	 	 	//求当前结点的左孩子树的深度 
	int d2= depth(T->rchild);   	 	 	//求当前结点的右孩子树的深度 

} 	return (d1>d2?d1:d2)+1; 	
```

注意:根据二叉树的结构特点,很多算法都可以用递归算法来实现。

## 24.编码实现直接插入排序

直接插入排序编程实现如下:

```
#include<iostream.h> 
void main( void ) 
{ 
 	int ARRAY[10] = { 0, 6, 3, 2, 7, 5, 4, 9, 1, 8 }; 
 	int i,j; 
 	for(  i = 0; i < 10; i++) 
 	{ 
 	 	cout<<ARRAY[i]<<" "; 
 	} 
 	cout<<endl; 
 	for( i = 2; i <= 10; i++ ) 	 	 	 	//将 ARRAY[2],…,ARRAY[n]依次按序插入 
 	{  	 
 	 	if(ARRAY[i] < ARRAY[i-1]) 	 	//如果 ARRAY[i]大于一切有序的数值, 
 	 	 	 	 	 	 	 	 	//ARRAY[i]将保持原位不动 
 	 	{ 
   ARRAY[0] = ARRAY[i];  //将 ARRAY[0]看做是哨兵,是 ARRAY[i]的副本    j = i - 1; 

 	 	do{  	 	 	 	 	//从右向左在有序区 ARRAY[1..i-1]中  	 	 	 	 	 	 	 	 	//查找 ARRAY[i]的插入位置 

   ARRAY[j+1] = ARRAY[j];  //将数值大于 ARRAY[i]记录后移    j-- ; 
 	 	 	}while( ARRAY[0] < ARRAY[j] );  
 	 	 	ARRAY[j+1]=ARRAY[0];  	//ARRAY[i]插入到正确的位置上 
 	 	} 
 	} 
 	for(  i = 0; i < 10; i++) 
 	{ 
 	 	cout<<ARRAY[i]<<" "; 
 	} 
 	cout<<endl; 
} 
```


注意:所有为简化边界条件而引入的附加结点(元素)均可称为哨兵。引入哨兵后使得查找循环条件的时间大约减少了一半,对于记录数较大的文件节约的时间就相当可观。类似于排序这样使用频率非常高的算法,要尽可能地减少其运行时间。所以不能把上述算法中的哨兵视为雕虫小技。

注意:所有为简化边界条件而引入的附加结点(元素)均可称为哨兵。引入哨兵后使得查找循环条件的时间大约减少了一半,对于记录数较大的文件节约的时间就相当可观。类似于排序这样使用频率非常高的算法,要尽可能地减少其运行时间。所以不能把上述算法中的哨兵视为雕虫小技。

## 25.编码实现冒泡排序

冒泡排序编程实现如下:

```
#include <stdio.h> 
#define LEN 10  	 	 	 	 	 	 	//数组长度 

 void main( void ) 
 { 
 	int ARRAY[10] = { 0, 6, 3, 2, 7, 5, 4, 9, 1, 8 }; 	//待排序数组 
 	printf( "\n" ); 
	for( int a = 0; a < LEN; a++ ) 	 	 	 	//打印数组内容 
 	{ 
 	printf( "%d ", ARRAY[a] ); 
	} 
	int i = 0; int j = 0; 
	bool isChange;  	 	 	 	 	 	//设定交换标志 

for( i = 1; i < LEN; i++ ) 
{ 	 	 	 	 	 	 	 	 	//最多做 LEN-1 趟排序 
 	isChange = 0;  	 	 	 	 	//本趟排序开始前,交换标志应为假 
 	for( j = LEN-1; j >= i; j-- )  	 	 	//对当前无序区 ARRAY[i..LEN]自下向上扫描 
{ 
if( ARRAY[j+1] < ARRAY[j] ) 	 
{ 	 	 	 	 	 	 	//交换记录 
 	 	ARRAY[0] = ARRAY[j+1];  	//ARRAY[0]不是哨兵,仅做暂存单元 
 	 	ARRAY[j+1] = ARRAY[j];  	 	
 	 	ARRAY[j] = ARRAY[0]; 
 		isChange = 1;  	 	 	//发生了交换,故将交换标志置为真 
 	} 

} 
printf( "\n" ); 
for( a = 0; a < LEN; a++)  	 	 	//打印本次排序后数组内容 
 { 
 	printf( "%d ", ARRAY[a] ); 
} 
if( !isChange )  	 	 	 	 
{ 
 	break; 
} 	//本趟排序未发生交换,提前终止算法 
printf( "\n" ); return; 
} 	
```

​	


## 26.编码实现直接选择排序



![在这里插入图片描述](https://img-blog.csdnimg.cn/20190620150142604.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0Jvc3RvblJheUFsZW4=,size_16,color_FFFFFF,t_70)

注意:在直接选择排序中,具有相同关键码的对象可能会颠倒次序,因而直接选择排序算法是一种不稳定的排序方法。在本例中只是例举了简单的整形数组排序,肯定不会有什么问题。但是在复杂的数据元素序列组合中,只是根据单一的某一个关键值排序,直接选择排序则不保证其稳定性,这是直接选择排序的一个弱点。

## 27.编程实现堆排序

堆排序编程实现:

```
void createHeep(int ARRAY[], int sPoint, int Len) //生成大根堆
{
	while ((2 * sPoint + 1) < Len)
	{
		int mPoint = 2 * sPoint + 1;
		if ((2 * sPoint + 2) < Len)
		{
			if (ARRAY[2 * sPoint + 1] < ARRAY[2 * sPoint + 2])
			{
				mPoint = 2 * sPoint + 2;
			}
		}
		if (ARRAY[sPoint] < ARRAY[mPoint]) //堆被破坏,需要重新调整
		{
			int tmpData = ARRAY[sPoint]; //交换 sPoint 与 mPoint 的数据
			ARRAY[sPoint] = ARRAY[mPoint];
			ARRAY[mPoint] = tmpData;
			sPoint = mPoint;
		}
		else
		{
			break; //堆未破坏,不再需要调整
		}
	}
	return;
}
void heepSort(int ARRAY[], int Len) //堆排序
{
	int i = 0;
	for (i = (Len / 2 - 1); i >= 0; i--) //将 Hr[0,Lenght-1]建成大根堆
	{
		createHeep(ARRAY, i, Len);
	}
	for (i = Len - 1; i > 0; i--)
	{
		int tmpData = ARRAY[0]; //与最后一个记录交换
		ARRAY[0] = ARRAY[i];
		ARRAY[i] = tmpData;
		createHeep(ARRAY, 0, i); //将 H.r[0..i]重新调整为大根堆
	}
	return;
}
int main(void)
{
	int ARRAY[] = { 5, 4, 7, 3, 9, 1, 6, 8, 2 };
	printf("Before sorted:\n"); //打印排序前数组内容
	for (int i = 0; i < 9; i++)
	{
		printf("%d ", ARRAY[i]);
	}
	printf("\n");
	heepSort(ARRAY, 9); //堆排序
	printf("After sorted:\n"); //打印排序后数组内容
	for (i = 0; i < 9; i++)
	{
		printf("%d ", ARRAY[i]);
	}
	printf("\n");
}
```


说明:堆排序,虽然实现复杂,但是非常的实用。另外读者可是自己设计实现小堆排序的算法。虽然和大堆排序的实现过程相似,但是却可以加深对堆排序的记忆和理解。

## 28.编程实现基数排序

```
#include <stdio.h>
#include <malloc.h>
#define LEN 8
typedef struct node //队列结点
{
	int data;
	struct node * next;
}node, *QueueNode;
typedef struct Queue //队列
{
	QueueNode front;
	QueueNode rear;
}Queue, *QueueLink;
QueueLink CreateNullQueue(QueueLink &Q) //创建空队列
{
	Q = NULL;
	Q = (QueueLink)malloc(sizeof(Queue));
	if (NULL == Q)
	{
		printf("Fail to malloc null queue!\n");
		return NULL;
	}
	Q->front = (QueueNode)malloc(sizeof(node));
	Q->rear = (QueueNode)malloc(sizeof(node));
	if (NULL == Q->front || NULL == Q->rear)
	{
		printf("Fail to malloc a new queue's fornt or rear!\n");
		return NULL;
	}
	Q->rear = NULL;
	Q->front->next = Q->rear;
	return Q;
}
int lenData(node data[], int len) //计算队列中各结点的数据的最大位数
{
	int m = 0;
	int temp = 0;
	int d;
	for (int i = 0; i < len; i++)
	{
		d = data[i].data;
		while (d > 0)
		{
			d /= 10;
			temp++;
		}
		if (temp > m)
		{
			m = temp;
		}
		temp = 0;
	}
	return m;
}
QueueLink Push(QueueLink &Q, node node) //将数据压入队列
{
	QueueNode p1, p;
	p = (QueueNode)malloc(sizeof(node));
	if (NULL == p)
	{
		printf("Fail to malloc a new node!\n");
		return NULL;
	}
	p1 = Q->front;
	while (p1->next != NULL)
	{
		p1 = p1->next;
	}
	p->data = node.data;
	p1->next = p;
	p->next = Q->rear;
	return NULL;
}
node Pop(QueueLink &Q) //数据出队列
{
	node temp;
	temp.data = 0;
	temp.next = NULL;
	QueueNode p;
	p = Q->front->next;
	if (p != Q->rear)
	{
		temp = *p;
		Q->front->next = p->next;
		free(p);
		p = NULL;
	}
	return temp;
}
int IsEmpty(QueueLink Q)
{
	if (Q->front->next == Q->rear)
	{
		return 0;
	}
	return 1;
}
int main(void)
{
	int i = 0;
	int Max = 0; //记录结点中数据的最大位数
	int d = 10;
	int power = 1;
	int k = 0;
	node Array[LEN] = { { 450, NULL }, { 32, NULL }, { 781, NULL }, { 57, NULL }, 组
	{ 145, NULL }, { 613, NULL }, { 401, NULL }, { 594, NULL } };
	//队列结点数
	QueueLink Queue[10];
	for (i = 0; i < 10; i++)
	{
		CreateNullQueue(Queue[i]); //初始化队列数组
	}
	for (i = 0; i < LEN; i++)
	{
		printf("%d ", Array[i].data);
	}
	printf("\n");
	Max = lenData(Array, LEN); //计算数组中关键字的最大位数
	printf("%d\n", Max);
	for (int j = 0; j < Max; j++) //按位排序
	{
		if (j == 0) power = 1;
		else power = power *d;
		for (i = 0; i < LEN; i++)
		{
			k = Array[i].data / power - (Array[i].data / (power * d)) * d;
			Push(Queue[k], Array[i]);
		}
		for (int l = 0, k = 0; l < d; l++) //排序后出队列重入数组
		{
			while (IsEmpty(Queue[l]))
			{
				Array[k++] = Pop(Queue[l]);
			}
		}
		for (int t = 0; t < LEN; t++)
		{
			printf("%d ", Array[t].data);
		}
		printf("\n");
	}
	return 0;
}
```


说明:队列为基数排序的实现提供了很大的方便,适当的数据机构可以减少算法的复杂度,让更多的算法实现更容易。

## 29.谈谈你对编程规范的理解或认识

编程规范可总结为:程序的可行性,可读性、可移植性以及可测试性。
说明:这是编程规范的总纲目,面试者不一定要去背诵上面给出的那几个例子,应该去理解这几个例子说明的问题,想一想,自己如何解决可行性、可读性、可移植性以及可测试性这几个问题,结合以上几个例子和自己平时的编程习惯来回答这个问题。

## 30.short i = 0; i = i + 1L;这两句有错吗

代码一是错的,代码二是正确的。
说明:在数据安全的情况下大类型的数据向小类型的数据转换一定要显示的强制类型转换。

## 31.&&和&、||和|有什么区别

(1)&和|对操作数进行求值运算,&&和||只是判断逻辑关系。(2)&&和||在在判断左侧操作数就能确定结果的情况下就不再对右侧操作数求值。
注意:在编程的时候有些时候将&&或||替换成&或|没有出错,但是其逻辑是错误的,可能会导致不可预想的后果(比如当两个操作数一个是 1 另一个是 2 时。

## 32.C++的引用和 C 语言的指针有什么区别

指针和引用主要有以下区别:
(1) 引用必须被初始化,但是不分配存储空间。指针不声明时初始化,在初始化的时候需要分配存储空间。
(2) 引用初始化以后不能被改变,指针可以改变所指的对象。
(3) 不存在指向空值的引用,但是存在指向空值的指针。
注意:引用作为函数参数时,会引发一定的问题,因为让引用作参数,目的就是想改变这个引用所指向地址的内容,而函数调用时传入的是实参,看不出函数的参数是正常变量,还是引用,因此可能会引发错误。所以使用时一定要小心谨慎。

## 33.在二元树中找出和为某一值的所有路径

输入一个整数和一棵二元树。从树的根结点开始往下访问,一直到叶结点所经过的所有结点形成一条路径。打印出和与输入整数相等的所有路径。例如,输入整数 9 和如下二元树:

则打印出两条路径:3,6 和 3,2,4。
【答案】

```
typedef struct path 
{ 
 	BiTNode* tree;  	 	 	 	 	//结点数据成员 
 	struct path* next;  	 
}PATH,*pPath; 	 	 	 	//结点指针成员 
//初始化树的结点栈: 
void init_path( pPath* L ) 
{ 
 	*L = ( pPath )malloc( sizeof( PATH ) ); 
 	( *L )->next = NULL;  
} 	 	//创建空树 
//树结点入栈函数: 
void push_path(pPath H, pBTree T) 
{ 
 	pPath p = H->next; 
 	pPath q = H; 
 	while( NULL != p ) 
 	{ 
 	q = p; 
 	p = p->next; 
} 
p = ( pPath )malloc( sizeof( PATH ) ); 	 	//申请新结点 
p->next = NULL; 	 	 	 	 	//初始化新结点 
} 	p->tree = T;  
q->next = p;  	 	 	 	 	 	//新结点入栈 
//树结点打印函数: 
void print_path( pPath L ) 
{ 
 	pPath p = L->next;  

while( NULL != p ) 	 	 	 
{ 
	printf("%d, ", p->tree->data);   p = p->next;  
}  

} 	 	//打印当前栈中所有数据 

///树结点出栈函数: 

void pop_path( pPath H ) 
{
	pPath p = H->next; 
	pPath q = H; 
	if( NULL == p )  	 	 	 	 	//检验当前栈是否为空 
 	{ 
 		printf("Stack is null!\n"); 
 		return; 
	} 
	p = p->next; 
	while( NULL != p ) 	 	 	 	 	//出栈 
	{ 
 		q = q->next;  	
 		p = p->next; 
	} 
	free( q->next );  	 	 	 	 	//释放出栈结点空间 
	q->next = NULL;  
}			
//判断结点是否为叶子结点: 
int IsLeaf(pBTree T) 
{ 
 	return ( T->lchild == NULL )&&( T->rchild==NULL );  
} 
//查找符合条件的路径: 
int find_path(pBTree T, int sum, pPath L) 
{ 
 	push_path( L, T); record += T->data;  
 	if( ( record == sum ) && ( IsLeaf( T ) ) ) 	 	//打印符合条件的当前路径 
	{ 
 		print_path( L );  
 		printf( "\n" );  
	} 
	if( T->lchild != NULL ) 	 	 	 	 	//递归查找当前节点的左孩子 
	{ 
 		find_path( T->lchild, sum, L); 
	} 
	if( T->rchild != NULL )  	 	 	 	 	//递归查找当前节点的右孩子 
	{ 
 		find_path( T->rchild, sum, L); 
	} 
	record -= T->data;  pop_path(L);  return 0; 	
}	
```


注意:数据结构一定要活学活用,例如本题,把所有的结点都压入栈,而不符合条件的结点弹出栈,很容易实现了有效路径的查找。虽然用链表也可以实现,但是用栈更利于理解这个问题,即适当的数据结构为更好的算法设计提供了有利的条件。

注意:数据结构一定要活学活用,例如本题,把所有的结点都压入栈,而不符合条件的结点弹出栈,很容易实现了有效路径的查找。虽然用链表也可以实现,但是用栈更利于理解这个问题,即适当的数据结构为更好的算法设计提供了有利的条件。

## 34.写一个“标准”宏 MIN

写一个“标准”宏 MIN,这个宏输入两个参数并且返回较小的一个。
【答案】

```
#define min(a,b)((a)<=(b)?(a):(b)) 
```

注意:在调用时一定要注意这个宏定义的副作用,如下调用:
((++*p)<=(x)?(++*p):(x)
p 指针就自加了两次,违背了 MIN 的本意。

## 35.typedef 和 define 有什么区别

(1) 用法不同:typedef 用来定义一种数据类型的别名,增强程序的可读性。define 主要用来定义常量,以及书写复杂使用频繁的宏。
(2) 执行时间不同:typedef 是编译过程的一部分,有类型检查的功能。define 是宏定义,是预编译的部分,其发生在编译之前,只是简单的进行字符串的替换,不进行类型的检查。
(3) 作用域不同:typedef 有作用域限定。define 不受作用域约束,只要是在 define 声明后的引用都是正确的。
(4) 对指针的操作不同:typedef 和 define 定义的指针时有很大的区别。
注意:typedef 定义是语句,因为句尾要加上分号。而 define 不是语句,千万不能在句尾加分号。

## 36.关键字 const 是什么

const 用来定义一个只读的变量或对象。主要优点:便于类型检查、同宏定义一样可以方便地进行参数的修改和调整、节省空间,避免不必要的内存分配、可为函数重载提供参考。
说明:const 修饰函数参数,是一种编程规范的要求,便于阅读,一看即知这个参数不能被改变,实现时不易出错。 const修饰成员函数不可修改成员变量。

## 37.static 有什么作用

static 在 C 中主要用于定义全局静态变量、定义局部静态变量、定义静态函数。在 C++中新增了两种作用:定义静态数据成员、静态函数成员。
注意:因为 static 定义的变量分配在静态区,所以其定义的变量的默认值为 0,普通变量的默认值为随机数,在定义指针变量时要特别注意。

## 38.extern 有什么作用

extern 标识的变量或者函数声明其定义在别的文件中,提示编译器遇到此变量和函数时在其它模块中寻找其定义。

## 39.流操作符重载为什么返回引用

在程序中,流操作符>>和<<经常连续使用。因此这两个操作符的返回值应该是一个仍旧支持这两个操作符的流引用。其他的数据类型都无法做到这一点。
注意:除了在赋值操作符和流操作符之外的其他的一些操作符中,如+、-、*、/等却千万不能返回引用。因为这四个操作符的对象都是右值,因此,它们必须构造一个对象作为返回值。

## 40.简述指针常量与常量指针区别

指针常量是指定义了一个指针,这个指针的值只能在定义时初始化,其他地方不能改变。常量指针是指定义了一个指针,这个指针指向一个只读的对象,不能通过常量指针来改变这个对象的值。
指针常量强调的是指针的不可改变性,而常量指针强调的是指针对其所指对象的不可改变性。
注意:无论是指针常量还是常量指针,其最大的用途就是作为函数的形式参数,保证实参在被调用函数中的不可改变特性。

## 41.数组名和指针的区别

请写出以下代码的打印结果:

```
#include <iostream.h>
#include <string.h>
void main(void)
{
	char str[13]="Hello world!";
	char *pStr="Hello world!";
	cout<<sizeof(str)<<endl;
	cout<<sizeof(pStr)<<endl;
	cout<<strlen(str)<<endl;
	cout<<strlen(pStr)<<endl;
	return;
}
```

【答案】
打印结果:
13
4
12 12
注意:一定要记得数组名并不是真正意义上的指针,它的内涵要比指针丰富的多。但是当数组名当做参数传递给函数后,其失去原来的含义,变作普通的指针。另外要注意 sizeof 不是函数,只是操作符。

## 42.如何避免“野指针”

“野指针”产生原因及解决办法如下:
(1) 指针变量声明时没有被初始化。解决办法:指针声明时初始化,可以是具体的地址值,也可让它指向 NULL。
(2) 指针 p 被 free 或者 delete 之后,没有置为 NULL。解决办法:指针指向的内存空间被释放后指针应该指向 NULL。
(3) 指针操作超越了变量的作用范围。解决办法:在变量的作用域结束前释放掉变量的地址空间并且让指针指向 NULL。
注意:“野指针”的解决方法也是编程规范的基本原则,平时使用指针时一定要避免产生“野指针”,在使用指针前一定要检验指针的合法性。

## 43.常引用有什么作用

常引用的引入主要是为了避免使用变量的引用时,在不知情的情况下改变变量的值。常引用主要用于定义一个普通变量的只读属性的别名、作为函数的传入形参,避免实参在调用函数中被意外的改变。
说明:很多情况下,需要用常引用做形参,被引用对象等效于常对象,不能在函数中改变实参的值,这样的好处是有较高的易读性和较小的出错率。

## 44.编码实现字符串转化为数字

编码实现函数 atoi(),设计一个程序,把一个字符串转化为一个整型数值。例如数字:“5486321”,转化成字符:5486321。
【答案】

```
int myAtoi(const char * str)
{
	int num = 0; //保存转换后的数值
	int isNegative = 0; //记录字符串中是否有负号
	int n = 0;
	char *p = str;
	if (p == NULL) //判断指针的合法性
	{
		return -1;
	}
	while (*p++ != '\0') //计算数字符串度
	{
		n++;
	}
	p = str;
	if (p[0] == '-') //判断数组是否有负号
	{
		isNegative = 1;
	}
	char temp = '0';
	for (int i = 0; i < n; i++)
	{
		char temp = *p++;
		if (temp > '9' || temp < '0') //滤除非数字字符
		{
			continue;
		}
		if (num != 0 || temp != '0') //滤除字符串开始的 0 字符
		{
			temp -= 0x30; //将数字字符转换为数值
			num += temp *int(pow(10, n - 1 - i));
		}
	}
	if (isNegative) //如果字符串中有负号,将数值取反
	{
		return (0 - num);
	}
	else
	{
		return num; //返回转换后的数值
	}
}
```


注意:此段代码只是实现了十进制字符串到数字的转化,读者可以自己去实现 2 进制,8 进制,10 进制,16 进制的转化。

## 45.简述 strcpy、sprintf 与 memcpy 的区别

三者主要有以下不同之处:
(1) 操作对象不同,strcpy 的两个操作对象均为字符串,sprintf 的操作源对象可以是多种数据类型,目的操作对象是字符串,memcpy 的两个对象就是两个任意可操作的内存地址,并不限于何种数据类型。
(2) 执行效率不同,memcpy 最高,strcpy 次之,sprintf 的效率最低。
(3) 实现功能不同,strcpy 主要实现字符串变量间的拷贝,sprintf 主要实现其他数据类型格式到字符串的转化,memcpy 主要是内存块间的拷贝。
说明:strcpy、sprintf 与 memcpy 都可以实现拷贝的功能,但是针对的对象不同,根据实际需求,来选择合适的函数实现拷贝功能。

## 46.用 C 编写一个死循环程序

while(1)
{ }
说明:很多种途径都可实现同一种功能,但是不同的方法时间和空间占用度不同,特别是对于嵌入式软件,处理器速度比较慢,存储空间较小,所以时间和空间优势是选择各种方法的首要考虑条件。

## 47.编码实现某一变量某位清 0 或置 1

给定一个整型变量 a,写两段代码,第一个设置 a 的 bit 3,第二个清 a 的 bit 3,在以上两个操作中,要保持其他位不变。
【答案】

```
#define BIT3 (0x1 << 3 ) 
Satic int a; 	
//设置 a 的 bit 3: 	
void set_bit3( void ) 
{ 
 	a |= BIT3;  	 	 	 	 
} 	//将 a 第 3 位置 1 
//清 a 的 bit 3 	
void set_bit3( void ) 
{ 
 	a &= ~BIT3;  	 	 	 	 
} 	//将 a 第 3 位清零 
```


说明:在置或清变量或寄存器的某一位时,一定要注意不要影响其他位。所以用加减法是很难实现的。

## 48.评论下面这个中断函数

中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展——让标准 C 支持中断。具体代表事实是,产生了一个新的关键字__interrupt。下面的代码就使用了__interrupt 关键字去定义一个中断服务子程序(ISR),请评论以下这段代码。

```
__interrupt double compute_area (double radius)  
{  
	double area = PI * radius * radius;  printf(" Area = %f", area);  return area;  
} 
```

【答案】
这段中断服务程序主要有以下四个问题:
(1) ISR 不能返回一个值。
(2) ISR 不能传递参数。
(3) 在 ISR 中做浮点运算是不明智的。
(4) printf()经常有重入和性能上的问题。
注意:本题的第三个和第四个问题虽不是考察的重点,但是如果能提到这两点可给面试官留下一个好印象。

## 49.构造函数能否为虚函数

构造函数不能是虚函数。而且不能在构造函数中调用虚函数,因为那样实际执行的是父类的对应函数,因为自己还没有构造好。析构函数可以是虚函数,而且,在一个复杂类结构中,这往往是必须的。
析构函数也可以是纯虚函数,但纯虚析构函数必须有定义体,因为析构函数的调用是在子类中隐含的。
说明:虚函数的动态绑定特性是实现重载的关键技术,动态绑定根据实际的调用情况查询相应类的虚函数表,调用相应的虚函数。

## 50.谈谈你对面向对象的认识

面向对象可以理解成对待每一个问题,都是首先要确定这个问题由几个部分组成,而每一个部分其实就是一个对象。然后再分别设计这些对象,最后得到整个程序。传统的程序设计多是基于功能的思想来进行考虑和设计的,而面向对象的程序设计则是基于对象的角度来考虑问题。这样做能够使得程序更加的简洁清晰。
说明:编程中接触最多的“面向对象编程技术”仅仅是面向对象技术中的一个组成部分。发挥面向对象技术的优势是一个综合的技术问题,不仅需要面向对象的分析,设计和编程技术,而且需要借助必要的建模和开发工具。

## 51.const、static作用。

## 52.c++面向对象三大特征及对他们的理解,引出多态实现原理、动态绑定、菱形继承。

## 53.虚析构的必要性,引出内存泄漏,虚函数和普通成员函数的储存位置,虚函数表、虚函数表指针

## 54.malloc、free和new、delete区别,引出malloc申请大内存、malloc申请空间失败怎么办

## 55.stl熟悉吗,vector、map、list、hashMap,vector底层,map引出红黑树。优先队列用过吗,使用的场景。无锁队列听说过吗,原理是什么(比较并交换)

## 56.实现擅长的排序,说出原理(快排、堆排)

## 57.四种cast,智能指针

## 58.tcp和udp区别

## 59.进程和线程区别

## 60.指针和引用作用以及区别

## 61.c++11用过哪些特性,auto作为返回值和模板一起怎么用,函数指针能和auto混用吗

## 62.boost用过哪些类,thread、asio、signal、bind、function

## 63.单例、工厂模式、代理、适配器、模板,使用场景

## 64.QT信号槽实现机制,QT内存管理,MFC消息机制

## 65.进程间通信。会选一个详细问

## 66.多线程,锁和信号量,互斥和同步

## 67.动态库和静态库的区别

```
//auto作为返回值和模板一起怎么用,函数指针能和auto混用吗

#include <iostream>

using namespace std;

template <typename T,typename U>
auto add(T t,U u) -> decltype(t+u)
{
	return t+u;
}

template <typename T,typename U>
auto sub(T t,U u) -> decltype(t-u)
{
        return t-u;
}

template <typename T,typename U>
auto pro(T t,U u) -> decltype(t*u)
{
        return t*u;
}

template <typename T,typename U>
auto div(T t,U u) -> decltype(t/u)
{
    try
	{
		return t/u;
	} 
	catch(...)
	{
		exit(0);
	}	
}


int main()
{
	int x = 520;
	double y= 13.14;
	auto z = add(x,y);
	cout<<z<<endl;
	

//auto(*funp[4])(int ,double) = {add,sub,pro,div};//error
double (*funp[4])(int ,double) = {add,sub,pro,div};
for(unsigned char i=0;i<4;i++)
{
   cout<<funp[i](x,y)<<endl;
}
return 0;

}
```

## 68.提高c++性能,你用过哪些方式去提升

(构造、析构、返回值优化、临时对象(使用operator=()消除临时对象)、内联(内联技巧、条件内联、递归内联、静态局部变量内联)、内存池、使用函数对象不使用函数指针、编码(编译器优化、预先计算)、设计(延迟计算、高效数据结构)、系统体系结构(寄存器、缓存、上下文切换))

## 69.编译原理,尝试自己写过语言或语言编译器

## 70.泛型模板实用度高

## 71.对多种计算机语言熟悉

## 72.Git项目了解多少

## 73.针对网络框架(DPDK)、逆向工程(汇编)、分布式集群(docker、k8s、redis等)、CPU计算(nvidia cuda)、图像识别(opencv、opengl、tensorflow等)、AI等有研究

版权声明:本文为CSDN博主「bostonAlen」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/BostonRayAlen/article/details/93041395

## 74.引用和指针的区别?

1)指针是一个实体,需要分配内存空间。引用只是变量的别名,不需要分配内存空间。
2)引用在定义的时候必须进行初始化,并且不能够改变。指针在定义的时候不一定要初始化,并且指向的空间可变。(注:不能有引用的值不能为NULL)
3)有多级指针,但是没有多级引用,只能有一级引用。
4)指针和引用的自增运算结果不一样。(指针是指向下一个空间,引用时引用的变量值加1)
5)sizeof 引用得到的是所指向的变量(对象)的大小,而sizeof 指针得到的是指针本身的大小。
6)引用访问一个变量是直接访问,而指针访问一个变量是间接访问。
7)使用指针前最好做类型检查,防止野指针的出现;
8)引用底层是通过指针实现的;
9)作为参数时也不同,传指针的实质是传值,传递的值是指针的地址;传引用的实质是传地址,传递的是变量的地址。

## 75.从汇编层去解释一下引用

```
1.9:          int x = 1;
2.00401048    mov         dword ptr [ebp-4],1
3.10:         int &b = x;
4.0040104F    lea         eax,[ebp-4]
5.00401052    mov         dword ptr [ebp-8],eax
```


x的地址为ebp-4,b的地址为ebp-8,因为栈内的变量内存是从高往低进行分配的。所以b的地址比x的低。lea eax,[ebp-4] 这条语句将x的地址ebp-4放入eax寄存器mov dword ptr [ebp-8],eax 这条语句将eax的值放入b的地址ebp-8中上面两条汇编的作用即:将x的地址存入变量b中,这不和将某个变量的地址存入指针变量是一样的吗?所以从汇编层次来看,的确引用是通过指针来实现的。

## 76.C++中的指针参数传递和引用参数传递

1)指针参数传递本质上是值传递,它所传递的是一个地址值。值传递过程中,被调函数的形式参数作为被调函数的局部变量处理,会在栈中开辟内存空间以存放由主调函数传递进来的实参值,从而形成了实参的一个副本(替身)。值传递的特点是,被调函数对形式参数的任何操作都是作为局部变量进行的,不会影响主调函数的实参变量的值(形参指针变了,实参指针不会变)。
2)引用参数传递过程中,被调函数的形式参数也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参(本体)的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量(根据别名找到主调函数中的本体)。因此,被调函数对形参的任何操作都会影响主调函数中的实参变量。
3)引用传递和指针传递是不同的,虽然他们都是在被调函数栈空间上的一个局部变量,但是任何对于引用参数的处理都会通过一个间接寻址的方式操作到主调函数中的相关变量。而对于指针传递的参数,如果改变被调函数中的指针地址,它将应用不到主调函数的相关变量。如果想通过指针参数传递来改变主调函数中的相关变量(地址),那就得使用指向指针的指针或者指针引用。
4)从编译的角度来讲,程序在编译时分别将指针和引用添加到符号表上,符号表中记录的是变量名及变量所对应地址。指针变量在符号表上对应的地址值为指针变量的地址值,而引用在符号表上对应的地址值为引用对象的地址值(与实参名字不同,地址相同)。符号表生成之后就不会再改,因此指针可以改变其指向的对象(指针变量中的值可以改),而引用对象则不能修改。

## 77.形参与实参的区别?

1)形参变量只有在被调用时才分配内存单元,在调用结束时, 即刻释放所分配的内存单元。因此,形参只有在函数内部有效。 函数调用结束返回主调函数后则不能再使用该形参变量。
2)实参可以是常量、变量、表达式、函数等, 无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值, 以便把这些值传送给形参。 因此应预先用赋值,输入等办法使实参获得确定值,会产生一个临时变量。
3)实参和形参在数量上,类型上,顺序上应严格一致, 否则会发生“类型不匹配”的错误。
4)函数调用中发生的数据传送是单向的。 即只能把实参的值传送给形参,而不能把形参的值反向地传送给实参。 因此在函数调用过程中,形参的值发生改变,而实参中的值不会变化。
5)当形参和实参不是指针类型时,在该函数运行时,形参和实参是不同的变量,他们在内存中位于不同的位置,形参将实参的内容复制一份,在该函数运行结束的时候形参被释放,而实参内容不会改变。
1)值传递:有一个形参向函数所属的栈拷贝数据的过程,如果值传递的对象是类对象 或是大的结构体对象,将耗费一定的时间和空间。(传值)
2)指针传递:同样有一个形参向函数所属的栈拷贝数据的过程,但拷贝的数据是一个固定为4字节的地址。(传值,传递的是地址值)
3)引用传递:同样有上述的数据拷贝过程,但其是针对地址的,相当于为该数据所在的地址起了一个别名。(传地址)
4)效率上讲,指针传递和引用传递比值传递效率高。一般主张使用引用传递,代码逻辑上更加紧凑、清晰。

## 78.static的用法和作用?

1.先来介绍它的第一条也是最重要的一条:隐藏。(static函数,static变量均可)
当同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。
2.static的第二个作用是保持变量内容的持久。(static变量中的记忆功能和全局生存期)存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和static变量,只不过和全局变量比起来,static可以控制变量的可见范围,说到底static还是用来隐藏的。
3.static的第三个作用是默认初始化为0(static变量)
其实全局变量也具备这一属性,因为全局变量也存储在静态数据区。在静态数据区,内存中所有的字节默认值都是0x00,某些时候这一特点可以减少程序员的工作量。
4.static的第四个作用:C++中的类成员声明static
1)函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;
2)在模块内的static全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;
3)在模块内的static函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内;
4)在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;
5)在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。
类内:
6)static类对象必须要在类外进行初始化,static修饰的变量先于对象存在,所以static修饰的变量要在类外初始化;
7)由于static修饰的类成员属于类,不属于对象,因此static类成员函数是没有this指针的,this指针是指向本对象的指针。正因为没有this指针,所以static类成员函数不能访问非static的类成员,只能访问 static修饰的类成员;
8)static成员函数不能被virtual修饰,static成员不属于任何对象或实例,所以加上virtual没有任何实际意义;静态成员函数没有this指针,虚函数的实现是为每一个对象分配一个vptr指针,而vptr是通过this指针调用的,所以不能为virtual;虚函数的调用关系,this->vptr->ctable->virtual function

## 79.静态变量什么时候初始化

1)初始化只有一次,但是可以多次赋值,在主程序之前,编译器已经为其分配好了内存。
2)静态局部变量和全局变量一样,数据都存放在全局区域,所以在主程序之前,编译器已经为其分配好了内存,但在C和C++中静态局部变量的初始化节点又有点不太一样。在C中,初始化发生在代码执行之前,编译阶段分配好内存之后,就会进行初始化,所以我们看到在C语言中无法使用变量对静态局部变量进行初始化,在程序运行结束,变量所处的全局内存会被全部回收。
3)而在C++中,初始化时在执行相关代码时才会进行初始化,主要是由于C++引入对象后,要进行初始化必须执行相应构造函数和析构函数,在构造函数或析构函数中经常会需要进行某些程序中需要进行的特定操作,并非简单地分配内存。所以C++标准定为全局或静态对象是有首次用到时才会进行构造,并通过atexit()来管理。在程序结束,按照构造顺序反方向进行逐个析构。所以在C++中是可以使用变量对静态局部变量进行初始化的。

## 80.const?

1)阻止一个变量被改变,可以使用const关键字。在定义该const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;
2)对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const;
3)在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;
4)对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的成员变量,类的常对象只能访问类的常成员函数;
5)对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为“左值”。
6)const成员函数可以访问非const对象的非const数据成员、const数据成员,也可以访问const对象内的所有数据成员;
7)非const成员函数可以访问非const对象的非const数据成员、const数据成员,但不可以访问const对象的任意数据成员;
8)一个没有明确声明为const的成员函数被看作是将要修改对象中数据成员的函数,而且编译器不允许它为一个const对象所调用。因此const对象只能调用const成员函数。
9)const类型变量可以通过类型转换符const_cast将const类型转换为非const类型;
10)const类型变量必须定义的时候进行初始化,因此也导致如果类的成员变量有const类型的变量,那么该变量必须在类的初始化列表中进行初始化;
11)对于函数值传递的情况,因为参数传递是通过复制实参创建一个临时变量传递进函数的,函数内只能改变临时变量,但无法改变实参。则这个时候无论加不加const对实参不会产生任何影响。但是在引用或指针传递函数调用中,因为传进去的是一个引用或指针,这样函数内部可以改变引用或指针所指向的变量,这时const 才是实实在在地保护了实参所指向的变量。因为在编译阶段编译器对调用函数的选择是根据实参进行的,所以,只有引用传递和指针传递可以用是否加const来重载。一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开来。

## 81.const成员函数的理解和应用?

①const Stock & Stock::topval (②const Stock & s) ③const
①处const:确保返回的Stock对象在以后的使用中不能被修改
②处const:确保此方法不修改传递的参数 S
③处const:保证此方法不修改调用它的对象,const对象只能调用const成员函数,不能调用非const函数

## 82.指针和const的用法

1)当const修饰指针时,由于const的位置不同,它的修饰对象会有所不同。
2)int const p2中const修饰p2的值,所以理解为p2的值不可以改变,即p2只能指向固定的一个变量地址,但可以通过p2读写这个变量的值。顶层指针表示指针本身是一个常量
3)int const p1或者const int p1两种情况中const修饰p1,所以理解为p1的值不可以改变,即不可以给*p1赋值改变p1指向变量的值,但可以通过给p赋值不同的地址改变这个指针指向。底层指针表示指针所指向的变量是一个常量。
4)int const *const p;

## 83.mutable

1)如果需要在const成员方法中修改一个成员变量的值,那么需要将这个成员变量修饰为mutable。即用mutable修饰的成员变量不受const成员方法的限制;
2)可以认为mutable的变量是类的辅助状态,但是只是起到类的一些方面表述的功能,修改他的内容我们可以认为对象的状态本身并没有改变的。实际上由于const_cast的存在,这个概念很多时候用处不是很到了。

## 84.extern用法?

1)extern修饰变量的声明
如果文件a.c需要引用b.c中变量int v,就可以在a.c中声明extern int v,然后就可以引用变量v。
2)extern修饰函数的声明
如果文件a.c需要引用b.c中的函数,比如在b.c中原型是int fun(int mu),那么就可以在a.c中声明extern int fun(int mu),然后就能使用fun来做任何事情。就像变量的声明一样,extern int fun(int mu)可以放在a.c中任何地方,而不一定非要放在a.c的文件作用域的范围中。
3)extern修饰符可用于指示C或者C++函数的调用规范。
比如在C++中调用C库函数,就需要在C++程序中用extern “C”声明要引用的函数。这是给链接器用的,告诉链接器在链接的时候用C函数规范来链接。主要原因是C++和C程序编译完成后在目标代码中命名规则不同。

## 85.int转字符串字符串转int?strcat,strcpy,strncpy,memset,memcpy的内部实现?

c++11标准增加了全局函数std::to_string
可以使用std::stoi/stol/stoll等等函数
strcpy拥有返回值,有时候函数原本不需要返回值,但为了增加灵活性如支持链式表达,

## 86.深拷贝与浅拷贝?

1)浅复制 —-只是拷贝了基本类型的数据,而引用类型数据,复制后也是会发生引用,我们把这种拷贝叫做“(浅复制)浅拷贝”,换句话说,浅复制仅仅是指向被复制的内存地址,如果原地址中对象被改变了,那么浅复制出来的对象也会相应改变。
深复制 —-在计算机中开辟了一块新的内存地址用于存放复制的对象。

2)在某些状况下,类内成员变量需要动态开辟堆内存,如果实行位拷贝,也就是把对象里的值完全复制给另一个对象,如A=B。这时,如果B中有一个成员变量指针已经申请了内存,那A中的那个成员变量也指向同一块内存。这就出现了问题:当B把内存释放了(如:析构),这时A内的指针就是野指针了,出现运行错误。

## 87.C++模板是什么,底层怎么实现的?

1)编译器并不是把函数模板处理成能够处理任意类的函数;编译器从函数模板通过具体类型产生不同的函数;编译器会对函数模板进行两次编译:在声明的地方对模板代码本身进行编译,在调用的地方对参数替换后的代码进行编译。
2)这是因为函数模板要被实例化后才能成为真正的函数,在使用函数模板的源文件中包含函数模板的头文件,如果该头文件中只有声明,没有定义,那编译器无法实例化该模板,最终导致链接错误。

## 88.C语言struct和C++struct区别

1)C语言中:struct是用户自定义数据类型(UDT);C++中struct是抽象数据类型(ADT),支持成员函数的定义,(C++中的struct能继承,能实现多态)。
2)C中struct是没有权限的设置的,且struct中只能是一些变量的集合体,可以封装数据却不可以隐藏数据,而且成员不可以是函数。
3)C++中,struct的成员默认访问说明符为public(为了与C兼容),class中的默认访问限定符为private,struct增加了访问权限,且可以和类一样有成员函数。
4)struct作为类的一种特例是用来自定义数据结构的。一个结构标记声明后,在C中必须在结构标记前加上struct,才能做结构类型名

## 89.虚函数可以声明为inline吗?

1)虚函数用于实现运行时的多态,或者称为晚绑定或动态绑定。而内联函数用于提高效率。内联函数的原理是,在编译期间,对调用内联函数的地方的代码替换成函数代码。内联函数对于程序中需要频繁使用和调用的小函数非常有用。
2)虚函数要求在运行时进行类型确定,而内敛函数要求在编译期完成相关的函数替换;

## 90.类成员初始化方式?构造函数的执行顺序 ?为什么用成员初始化列表会快一些?

1)赋值初始化,通过在函数体内进行赋值初始化;列表初始化,在冒号后使用初始化列表进行初始化。
这两种方式的主要区别在于:
对于在函数体中初始化,是在所有的数据成员被分配内存空间后才进行的。
列表初始化是给数据成员分配内存空间时就进行初始化,就是说分配一个数据成员只要冒号后有此数据成员的赋值表达式(此表达式必须是括号赋值表达式),那么分配了内存空间后在进入函数体之前给数据成员赋值,就是说初始化这个数据成员此时函数体还未执行。
2)一个派生类构造函数的执行顺序如下:
①虚拟基类的构造函数(多个虚拟基类则按照继承的顺序执行构造函数)。
②基类的构造函数(多个普通基类也按照继承的顺序执行构造函数)。
③类类型的成员对象的构造函数(按照初始化顺序)
④派生类自己的构造函数。
3)方法一是在构造函数当中做赋值的操作,而方法二是做纯粹的初始化操作。我们都知道,C++的赋值操作是会产生临时对象的。临时对象的出现会降低程序的效率。

## 91.成员列表初始化?

1)必须使用成员初始化的四种情况
①当初始化一个引用成员时;
②当初始化一个常量成员时;
③当调用一个基类的构造函数,而它拥有一组参数时;
④当调用一个成员类的构造函数,而它拥有一组参数时;
2)成员初始化列表做了什么
①编译器会一一操作初始化列表,以适当的顺序在构造函数之内安插初始化操作,并且在任何显示用户代码之前;
②list中的项目顺序是由类中的成员声明顺序决定的,不是由初始化列表的顺序决定的;

## 92.构造函数为什么不能为虚函数?析构函数为什么要虚函数?

从存储空间角度,虚函数相应一个指向vtable虚函数表的指针,这大家都知道,但是这个指向vtable的指针事实上是存储在对象的内存空间的。问题出来了,假设构造函数是虚的,就须要通过 vtable来调用,但是对象还没有实例化,也就是内存空间还没有,怎么找vtable呢?所以构造函数不能是虚函数。
从使用角度,虚函数主要用于在信息不全的情况下,能使重载的函数得到相应的调用。构造函数本身就是要初始化实例,那使用虚函数也没有实际意义呀。所以构造函数没有必要是虚函数。虚函数的作用在于通过父类的指针或者引用来调用它的时候可以变成调用子类的那个成员函数。而构造函数是在创建对象时自己主动调用的,不可能通过父类的指针或者引用去调用,因此也就规定构造函数不能是虚函数。
构造函数不须要是虚函数,也不同意是虚函数,由于创建一个对象时我们总是要明白指定对象的类型,虽然我们可能通过实验室的基类的指针或引用去訪问它但析构却不一定,我们往往通过基类的指针来销毁对象。这时候假设析构函数不是虚函数,就不能正确识别对象类型从而不能正确调用析构函数。
从实现上看,vbtl在构造函数调用后才建立,因而构造函数不可能成为虚函数从实际含义上看,在调用构造函数时还不能确定对象的真实类型(由于子类会调父类的构造函数);并且构造函数的作用是提供初始化,在对象生命期仅仅运行一次,不是对象的动态行为,也没有必要成为虚函数。
当一个构造函数被调用时,它做的首要的事情之中的一个是初始化它的VPTR。因此,它仅仅能知道它是“当前”类的,而全然忽视这个对象后面是否还有继承者。当编译器为这个构造函数产生代码时,它是为这个类的构造函数产生代码——既不是为基类,也不是为它的派生类(由于类不知道谁继承它)。所以它使用的VPTR必须是对于这个类的VTABLE。并且,仅仅要它是最后的构造函数调用,那么在这个对象的生命期内,VPTR将保持被初始化为指向这个VTABLE, 但假设接着另一个更晚派生的构造函数被调用,这个构造函数又将设置VPTR指向它的 VTABLE,等.直到最后的构造函数结束。VPTR的状态是由被最后调用的构造函数确定的。这就是为什么构造函数调用是从基类到更加派生类顺序的还有一个理由。可是,当这一系列构造函数调用正发生时,每一个构造函数都已经设置VPTR指向它自己的VTABLE。假设函数调用使用虚机制,它将仅仅产生通过它自己的VTABLE的调用,而不是最后的VTABLE(全部构造函数被调用后才会有最后的VTABLE)。
因为构造函数本来就是为了明确初始化对象成员才产生的,然而virtual function主要是为了再不完全了解细节的情况下也能正确处理对象。另外,virtual函数是在不同类型的对象产生不同的动作,现在对象还没有产生,如何使用virtual函数来完成你想完成的动作。
直接的讲,C++中基类采用virtual虚析构函数是为了防止内存泄漏。具体地说,如果派生类中申请了内存空间,并在其析构函数中对这些内存空间进行释放。假设基类中采用的是非虚析构函数,当删除基类指针指向的派生类对象时就不会触发动态绑定,因而只会调用基类的析构函数,而不会调用派生类的析构函数。那么在这种情况下,派生类中申请的空间就得不到释放从而产生内存泄漏。所以,为了防止这种情况的发生,C++中基类的析构函数应采用virtual虚析构函数。

## 93.析构函数的作用,如何起作用?

1)构造函数只是起初始化值的作用,但实例化一个对象的时候,可以通过实例去传递参数,从主函数传递到其他的函数里面,这样就使其他的函数里面有值了。规则,只要你一实例化对象,系统自动回调用一个构造函数,就是你不写,编译器也自动调用一次。
2)析构函数与构造函数的作用相反,用于撤销对象的一些特殊任务处理,可以是释放对象分配的内存空间;特点:析构函数与构造函数同名,但该函数前面加~。 析构函数没有参数,也没有返回值,而且不能重载,在一个类中只能有一个析构函数。 当撤销对象时,编译器也会自动调用析构函数。 每一个类必须有一个析构函数,用户可以自定义析构函数,也可以是编译器自动生成默认的析构函数。一般析构函数定义为类的公有成员。

## 94.构造函数和析构函数可以调用虚函数吗,为什么?

1)在C++中,提倡不在构造函数和析构函数中调用虚函数;
2)构造函数和析构函数调用虚函数时都不使用动态联编,如果在构造函数或析构函数中调用虚函数,则运行的是为构造函数或析构函数自身类型定义的版本;
3)因为父类对象会在子类之前进行构造,此时子类部分的数据成员还未初始化,因此调用子类的虚函数时不安全的,故而C++不会进行动态联编;
4)析构函数是用来销毁一个对象的,在销毁一个对象时,先调用子类的析构函数,然后再调用基类的析构函数。所以在调用基类的析构函数时,派生类对象的数据成员已经销毁,这个时候再调用子类的虚函数没有任何意义。

## 95.构造函数的执行顺序?析构函数的执行顺序?构造函数内部干了啥?拷贝构造干了啥?

1)构造函数顺序
①基类构造函数。如果有多个基类,则构造函数的调用顺序是某类在类派生表中出现的顺序,而不是它们在成员初始化表中的顺序。
②成员类对象构造函数。如果有多个成员类对象则构造函数的调用顺序是对象在类中被声明的顺序,而不是它们出现在成员初始化表中的顺序。
③派生类构造函数。
2)析构函数顺序
①调用派生类的析构函数;
②调用成员类对象的析构函数;
③调用基类的析构函数。

## 96.虚析构函数的作用,父类的析构函数是否要设置为虚函数?

1)C++中基类采用virtual虚析构函数是为了防止内存泄漏。具体地说,如果派生类中申请了内存空间,并在其析构函数中对这些内存空间进行释放。假设基类中采用的是非虚析构函数,当删除基类指针指向的派生类对象时就不会触发动态绑定,因而只会调用基类的析构函数,而不会调用派生类的析构函数。那么在这种情况下,派生类中申请的空间就得不到释放从而产生内存泄漏。所以,为了防止这种情况的发生,C++中基类的析构函数应采用virtual虚析构函数。
2)纯虚析构函数一定得定义,因为每一个派生类析构函数会被编译器加以扩张,以静态调用的方式调用其每一个虚基类以及上一层基类的析构函数。因此,缺乏任何一个基类析构函数的定义,就会导致链接失败。因此,最好不要把虚析构函数定义为纯虚析构函数。

24.构造函数析构函数可以调用虚函数吗?
1)在构造函数和析构函数中最好不要调用虚函数;
2)构造函数或者析构函数调用虚函数并不会发挥虚函数动态绑定的特性,跟普通函数没区别;
3)即使构造函数或者析构函数如果能成功调用虚函数, 程序的运行结果也是不可控的。

## 97.构造函数析构函数可否抛出异常

C++只会析构已经完成的对象,对象只有在其构造函数执行完毕才算是完全构造妥当。在构造函数中发生异常,控制权转出构造函数之外。因此,在对象b的构造函数中发生异常,对象b的析构函数不会被调用。因此会造成内存泄漏。
2)用auto_ptr对象来取代指针类成员,便对构造函数做了强化,免除了抛出异常时发生资源泄漏的危机,不再需要在析构函数中手动释放资源;
3)如果控制权基于异常的因素离开析构函数,而此时正有另一个异常处于作用状态,C++会调用terminate函数让程序结束;
4)如果异常从析构函数抛出,而且没有在当地进行捕捉,那个析构函数便是执行不全的。如果析构函数执行不全,就是没有完成他应该执行的每一件事情。

## 98.类如何实现只能静态分配和只能动态分配

1)前者是把new、delete运算符重载为private属性。后者是把构造、析构函数设为protected属性,再用子类来动态创建
2)建立类的对象有两种方式:
①静态建立,静态建立一个类对象,就是由编译器为对象在栈空间中分配内存;
②动态建立,A *p = new A();动态建立一个类对象,就是使用new运算符为对象在堆空间中分配内存。这个过程分为两步,第一步执行operator new()函数,在堆中搜索一块内存并进行分配;第二步调用类构造函数构造对象;
3)只有使用new运算符,对象才会被建立在堆上,因此只要限制new运算符就可以实现类对象只能建立在栈上。可以将new运算符设为私有。

## 99.如果想将某个类用作基类,为什么该类必须定义而非声明?

1)派生类中包含并且可以使用它从基类继承而来的成员,为了使用这些成员,派生类必须知道他们是什么。

## 100.什么情况会自动生成默认构造函数?

1)带有默认构造函数的类成员对象,如果一个类没有任何构造函数,但它含有一个成员对象,而后者有默认构造函数,那么编译器就为该类合成出一个默认构造函数。不过这个合成操作只有在构造函数真正被需要的时候才会发生;如果一个类A含有多个成员类对象的话,那么类A的每一个构造函数必须调用每一个成员对象的默认构造函数而且必须按照类对象在类A中的声明顺序进行;
2)带有默认构造函数的基类,如果一个没有任务构造函数的派生类派生自一个带有默认构造函数基类,那么该派生类会合成一个构造函数调用上一层基类的默认构造函数;
3)带有一个虚函数的类
4)带有一个虚基类的类
5)合成的默认构造函数中,只有基类子对象和成员类对象会被初始化。所有其他的非静态数据成员都不会被初始化。

## 101.什么是类的继承?

1)类与类之间的关系
has-A包含关系,用以描述一个类由多个部件类构成,实现has-A关系用类的成员属性表示,即一个类的成员属性是另一个已经定义好的类;
use-A,一个类使用另一个类,通过类之间的成员函数相互联系,定义友元或者通过传递参数的方式来实现;
is-A,继承关系,关系具有传递性;
2)继承的相关概念
所谓的继承就是一个类继承了另一个类的属性和方法,这个新的类包含了上一个类的属性和方法,被称为子类或者派生类,被继承的类称为父类或者基类;
3)继承的特点
子类拥有父类的所有属性和方法,子类可以拥有父类没有的属性和方法,子类对象可以当做父类对象使用;
4)继承中的访问控制
public、protected、private
5)继承中的构造和析构函数
6)继承中的兼容性原则

## 102.什么是组合?

1)一个类里面的数据成员是另一个类的对象,即内嵌其他类的对象作为自己的成员;创建组合类的对象:首先创建各个内嵌对象,难点在于构造函数的设计。创建对象时既要对基本类型的成员进行初始化,又要对内嵌对象进行初始化。
2)创建组合类对象,构造函数的执行顺序:先调用内嵌对象的构造函数,然后按照内嵌对象成员在组合类中的定义顺序,与组合类构造函数的初始化列表顺序无关。然后执行组合类构造函数的函数体,析构函数调用顺序相反。

## 103.抽象基类为什么不能创建对象?

抽象类是一种特殊的类,它是为了抽象和设计的目的为建立的,它处于继承层次结构的较上层。
(1)抽象类的定义:
称带有纯虚函数的类为抽象类。
(2)抽象类的作用:
抽象类的主要作用是将有关的操作作为结果接口组织在一个继承层次结构中,由它来为派生类提供一个公共的根,派生类将具体实现在其基类中作为接口的操作。所以派生类实际上刻画了一组子类的操作接口的通用语义,这些语义也传给子类,子类可以具体实现这些语义,也可以再将这些语义传给自己的子类。
(3)使用抽象类时注意:
抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类中没有重新定义纯虚函数,而只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体的类。
抽象类是不能定义对象的。一个纯虚函数不需要(但是可以)被定义。

一、纯虚函数定义
 纯虚函数是一种特殊的虚函数,它的一般格式如下:
  class <类名>
  {
  virtual <类型><函数名>(<参数表>)=0;
  …
  };
  在许多情况下,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去做。这就是纯虚函数的作用。
  纯虚函数可以让类先具有一个操作名称,而没有操作内容,让派生类在继承时再去具体地给出定义。凡是含有纯虚函数的类叫做抽象类。这种类不能声明对象,只是作为基类为派生类服务。除非在派生类中完全实现基类中所有的的纯虚函数,否则,派生类也变成了抽象类,不能实例化对象。

二、纯虚函数引入原因
  1、为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。
  2、在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔 雀等子类,但动物本身生成对象明显不合常理。
  为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;)。若要使派生类为非抽象类,则编译器要求在派生类中,必须对纯虚函数予以重载以实现多态性。同时含有纯虚函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。
例如,绘画程序中,shape作为一个基类可以派生出圆形、矩形、正方形、梯形等, 如果我要求面积总和的话,那么会可以使用一个 shape * 的数组,只要依次调用派生类的area()函数了。如果不用接口就没法定义成数组,因为既可以是circle ,也可以是square ,而且以后还可能加上rectangle,等等.

三、相似概念
1、多态性
指相同对象收到不同消息或不同对象收到相同消息时产生不同的实现动作。C++支持两种多态性:编译时多态性,运行时多态性。
  a.编译时多态性:通过重载函数实现
  b.运行时多态性:通过虚函数实现。
2、虚函数
  虚函数是在基类中被声明为virtual,并在派生类中重新定义的成员函数,可实现成员函数的动态重载。
3、抽象类
  包含纯虚函数的类称为抽象类。由于抽象类包含了没有定义的纯虚函数,所以不能定义抽象类的对象。

## 104.类什么时候会析构?

1)对象生命周期结束,被销毁时;
2)delete指向对象的指针时,或delete指向对象的基类类型指针,而其基类虚构函数是虚函数时;
3)对象i是对象o的成员,o的析构函数被调用时,对象i的析构函数也被调用。

## 105.为什么友元函数必须在类内部声明?

1)因为编译器必须能够读取这个结构的声明以理解这个数据类型的大、行为等方面的所有规则。有一条规则在任何关系中都很重要,那就是谁可以访问我的私有部分。

## 106.介绍一下C++里面的多态?

(1)静态多态(重载,模板)
是在编译的时候,就确定调用函数的类型。
(2)动态多态(覆盖,虚函数实现)
在运行的时候,才确定调用的是哪个函数,动态绑定。运行基类指针指向派生类的对象,并调用派生类的函数。
虚函数实现原理:虚函数表和虚函数指针。
纯虚函数: virtual int fun() = 0;
函数的运行版本由实参决定,在运行时选择函数的版本,所以动态绑定又称为运行时绑定。
当编译器遇到一个模板定义时,它并不生成代码。只有当实例化出模板的一个特定版本时,编译器才会生成代码。

## 107.用C语言实现C++的继承

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

//C++中的继承与多态
struct A
{
    virtual void fun()    //C++中的多态:通过虚函数实现
    {
        cout<<"A:fun()"<<endl;
    }

int a;

};
struct B:public A         //C++中的继承:B类公有继承A类
{
    virtual void fun()    //C++中的多态:通过虚函数实现(子类的关键字virtual可加可不加)
    {
        cout<<"B:fun()"<<endl;
    }

int b;

};

//C语言模拟C++的继承与多态

typedef void (*FUN)();      //定义一个函数指针来实现对成员函数的继承

struct _A       //父类
{
    FUN _fun;   //由于C语言中结构体不能包含函数,故只能用函数指针在外面实现

int _a;

};

struct _B         //子类
{
    _A _a_;     //在子类中定义一个基类的对象即可实现对父类的继承
    int _b;
};

void _fA()       //父类的同名函数
{
    printf("_A:_fun()\n");
}
void _fB()       //子类的同名函数
{
    printf("_B:_fun()\n");
}


void Test()
{
    //测试C++中的继承与多态
    A a;    //定义一个父类对象a
    B b;    //定义一个子类对象b

A* p1 = &a;   //定义一个父类指针指向父类的对象
p1->fun();    //调用父类的同名函数
p1 = &b;      //让父类指针指向子类的对象
p1->fun();    //调用子类的同名函数

//C语言模拟继承与多态的测试
_A _a;    //定义一个父类对象_a
_B _b;    //定义一个子类对象_b
_a._fun = _fA;        //父类的对象调用父类的同名函数
_b._a_._fun = _fB;    //子类的对象调用子类的同名函数

_A* p2 = &_a;   //定义一个父类指针指向父类的对象
p2->_fun();     //调用父类的同名函数
p2 = (_A*)&_b;  //让父类指针指向子类的对象,由于类型不匹配所以要进行强转
p2->_fun();     //调用子类的同名函数

}
```

## 108.继承机制中对象之间如何转换?指针和引用之间如何转换?

1)向上类型转换
将派生类指针或引用转换为基类的指针或引用被称为向上类型转换,向上类型转换会自动进行,而且向上类型转换是安全的。
2)向下类型转换
将基类指针或引用转换为派生类指针或引用被称为向下类型转换,向下类型转换不会自动进行,因为一个基类对应几个派生类,所以向下类型转换时不知道对应哪个派生类,所以在向下类型转换时必须加动态类型识别技术。RTTI技术,用dynamic_cast进行向下类型转换。

## 109.组合与继承优缺点?

一:继承
继承是Is a 的关系,比如说Student继承Person,则说明Student is a Person。继承的优点是子类可以重写父类的方法来方便地实现对父类的扩展。
继承的缺点有以下几点:
①:父类的内部细节对子类是可见的。
②:子类从父类继承的方法在编译时就确定下来了,所以无法在运行期间改变从父类继承的方法的行为。
③:如果对父类的方法做了修改的话(比如增加了一个参数),则子类的方法必须做出相应的修改。所以说子类与父类是一种高耦合,违背了面向对象思想。
二:组合
组合也就是设计类的时候把要组合的类的对象加入到该类中作为自己的成员变量。
组合的优点:
①:当前对象只能通过所包含的那个对象去调用其方法,所以所包含的对象的内部细节对当前对象时不可见的。
②:当前对象与包含的对象是一个低耦合关系,如果修改包含对象的类中代码不需要修改当前对象类的代码。
③:当前对象可以在运行时动态的绑定所包含的对象。可以通过set方法给所包含对象赋值。
组合的缺点:①:容易产生过多的对象。②:为了能组合多个对象,必须仔细对接口进行定义。

## 110.左值右值

1)在C++11中所有的值必属于左值、右值两者之一,右值又可以细分为纯右值、将亡值。在C++11中可以取地址的、有名字的就是左值,反之,不能取地址的、没有名字的就是右值(将亡值或纯右值)。举个例子,int a = b+c, a 就是左值,其有变量名为a,通过&a可以获取该变量的地址;表达式b+c、函数int func()的返回值是右值,在其被赋值给某一变量前,我们不能通过变量名找到它,&(b+c)这样的操作则不会通过编译。
2)C++11对C++98中的右值进行了扩充。在C++11中右值又分为纯右值(prvalue,Pure Rvalue)和将亡值(xvalue,eXpiring Value)。其中纯右值的概念等同于我们在C++98标准中右值的概念,指的是临时变量和不跟对象关联的字面量值;将亡值则是C++11新增的跟右值引用相关的表达式,这样表达式通常是将要被移动的对象(移为他用),比如返回右值引用T&&的函数返回值、std::move的返回值,或者转换为T&&的类型转换函数的返回值。将亡值可以理解为通过“盗取”其他变量内存空间的方式获取到的值。在确保其他变量不再被使用、或即将被销毁时,通过“盗取”的方式可以避免内存空间的释放和分配,能够延长变量值的生命期。
3)左值引用就是对一个左值进行引用的类型。右值引用就是对一个右值进行引用的类型,事实上,由于右值通常不具有名字,我们也只能通过引用的方式找到它的存在。右值引用和左值引用都是属于引用类型。无论是声明一个左值引用还是右值引用,都必须立即进行初始化。而其原因可以理解为是引用类型本身自己并不拥有所绑定对象的内存,只是该对象的一个别名。左值引用是具名变量值的别名,而右值引用则是不具名(匿名)变量的别名。左值引用通常也不能绑定到右值,但常量左值引用是个“万能”的引用类型。它可以接受非常量左值、常量左值、右值对其进行初始化。不过常量左值所引用的右值在它的“余生”中只能是只读的。相对地,非常量左值只能接受非常量左值对其进行初始化。
4)右值值引用通常不能绑定到任何的左值,要想绑定一个左值到右值引用,通常需要std::move()将左值强制转换为右值。

## 111.移动构造函数

1)我们用对象a初始化对象b,后对象a我们就不在使用了,但是对象a的空间还在呀(在析构之前),既然拷贝构造函数,实际上就是把a对象的内容复制一份到b中,那么为什么我们不能直接使用a的空间呢?这样就避免了新的空间的分配,大大降低了构造的成本。这就是移动构造函数设计的初衷;
2)拷贝构造函数中,对于指针,我们一定要采用深层复制,而移动构造函数中,对于指针,我们采用浅层复制。浅层复制之所以危险,是因为两个指针共同指向一片内存空间,若第一个指针将其释放,另一个指针的指向就不合法了。所以我们只要避免第一个指针释放空间就可以了。避免的方法就是将第一个指针(比如a->value)置为NULL,这样在调用析构函数的时候,由于有判断是否为NULL的语句,所以析构a的时候并不会回收a->value指向的空间;
3)移动构造函数的参数和拷贝构造函数不同,拷贝构造函数的参数是一个左值引用,但是移动构造函数的初值是一个右值引用。意味着,移动构造函数的参数是一个右值或者将亡值的引用。也就是说,只用用一个右值,或者将亡值初始化另一个对象的时候,才会调用移动构造函数。而那个move语句,就是将一个左值变成一个将亡值。

## 112.C语言的编译链接过程?

源代码-->预处理-->编译-->优化-->汇编-->链接–>可执行文件
1)预处理
读取c源程序,对其中的伪指令(以#开头的指令)和特殊符号进行处理。包括宏定义替换、条件编译指令、头文件包含指令、特殊符号。 预编译程序所完成的基本上是对源程序的“替代”工作。经过此种替代,生成一个没有宏定义、没有条件编译指令、没有特殊符号的输出文件。.i预处理后的c文件,.ii预处理后的C++文件。
2)编译阶段
编译程序所要作得工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。.s文件
3)汇编过程
汇编过程实际上指把汇编语言代码翻译成目标机器指令的过程。对于被翻译系统处理的每一个C语言源程序,都将最终经过这一处理而得到相应的目标文件。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。.o目标文件
4)链接阶段
链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够诶操作系统装入执行的统一整体。

## 113.vector与list的区别与应用?怎么找某vector或者list的倒数第二个元素

1)vector数据结构
vector和数组类似,拥有一段连续的内存空间,并且起始地址不变。因此能高效的进行随机存取,时间复杂度为o(1);但因为内存空间是连续的,所以在进行插入和删除操作时,会造成内存块的拷贝,时间复杂度为o(n)。另外,当数组中内存空间不够时,会重新申请一块内存空间并进行内存拷贝。连续存储结构:vector是可以实现动态增长的对象数组,支持对数组高效率的访问和在数组尾端的删除和插入操作,在中间和头部删除和插入相对不易,需要挪动大量的数据。它与数组最大的区别就是vector不需程序员自己去考虑容量问题,库里面本身已经实现了容量的动态增长,而数组需要程序员手动写入扩容函数进形扩容。
2)list数据结构
list是由双向链表实现的,因此内存空间是不连续的。只能通过指针访问数据,所以list的随机存取非常没有效率,时间复杂度为o(n);但由于链表的特点,能高效地进行插入和删除。非连续存储结构:list是一个双链表结构,支持对链表的双向遍历。每个节点包括三个信息:元素本身,指向前一个元素的节点(prev)和指向下一个元素的节点(next)。因此list可以高效率的对数据元素任意位置进行访问和插入删除等操作。由于涉及对额外指针的维护,所以开销比较大。
区别:
vector的随机访问效率高,但在插入和删除时(不包括尾部)需要挪动数据,不易操作。list的访问要遍历整个链表,它的随机访问效率低。但对数据的插入和删除操作等都比较方便,改变指针的指向即可。list是单向的,vector是双向的。vector中的迭代器在使用后就失效了,而list的迭代器在使用之后还可以继续使用。
3)
int mySize = vec.size();vec.at(mySize -2);
list不提供随机访问,所以不能用下标直接访问到某个位置的元素,要访问list里的元素只能遍历,不过你要是只需要访问list的最后N个元素的话,可以用反向迭代器来遍历:

## 114.STL vector的实现,删除其中的元素,迭代器如何变化?为什么是两倍扩容?释放空间?

size()函数返回的是已用空间大小,capacity()返回的是总空间大小,capacity()-size()则是剩余的可用空间大小。当size()和capacity()相等,说明vector目前的空间已被用完,如果再添加新元素,则会引起vector空间的动态增长。
由于动态增长会引起重新分配内存空间、拷贝原空间、释放原空间,这些过程会降低程序效率。因此,可以使用reserve(n)预先分配一块较大的指定大小的内存空间,这样当指定大小的内存空间未使用完时,是不会重新分配内存空间的,这样便提升了效率。只有当n>capacity()时,调用reserve(n)才会改变vector容量。
resize()成员函数只改变元素的数目,不改变vector的容量。

- 空的vector对象,size()和capacity()都为0

- 当空间大小不足时,新分配的空间大小为原空间大小的2倍。
- 使用reserve()预先分配一块内存后,在空间未满的情况下,不会引起重新分配,从而提升了效率。
- 当reserve()分配的空间比原空间小时,是不会引起重新分配的。
- resize()函数只改变容器的元素数目,未改变容器大小。
- 用reserve(size_type)只是扩大capacity值,这些内存空间可能还是“野”的,如果此时使用“[ ]”来访问,则可能会越界。而resize(size_type new_size)会真正使容器具有new_size个对象。

1.不同的编译器,vector有不同的扩容大小。在vs下是1.5倍,在GCC下是2倍;
2.空间和时间的权衡。简单来说, 空间分配的多,平摊时间复杂度低,但浪费空间也多。
3.使用k=2增长因子的问题在于,每次扩展的新尺寸必然刚好大于之前分配的总和,也就是说,之前分配的内存空间不可能被使用。这样对内存不友好。最好把增长因子设为(1,2)

4.对比可以发现采用采用成倍方式扩容,可以保证常数的时间复杂度,而增加指定大小的容量只能达到O(n)的时间复杂度,因此,使用成倍的方式扩容。

如何释放空间:
由于vector的内存占用空间只增不减,比如你首先分配了10,000个字节,然后erase掉后面9,999个,留下一个有效元素,但是内存占用仍为10,000个。所有内存空间是在vector析构时候才能被系统回收。empty()用来检测容器是否为空的,clear()可以清空所有元素。但是即使clear(),vector所占用的内存空间依然如故,无法保证内存的回收。
如果需要空间动态缩小,可以考虑使用deque。如果vector,可以用swap()来帮助你释放内存。
vector(Vec).swap(Vec);
将Vec的内存空洞清除;
vector().swap(Vec);
清空Vec的内存;

## 115.容器内部删除一个元素

1)顺序容器
erase迭代器不仅使所指向被删除的迭代器失效,而且使被删元素之后的所有迭代器失效(list除外),所以不能使用erase(it++)的方式,但是erase的返回值是下一个有效迭代器;
It = c.erase(it);
2)关联容器
erase迭代器只是被删除元素的迭代器失效,但是返回值是void,所以要采用erase(it++)的方式删除迭代器;
c.erase(it++)

## 116.STL迭代器如何实现

1.迭代器是一种抽象的设计理念,通过迭代器可以在不了解容器内部原理的情况下遍历容器,除此之外,STL中迭代器一个最重要的作用就是作为容器与STL算法的粘合剂。
2.迭代器的作用就是提供一个遍历容器内部所有元素的接口,因此迭代器内部必须保存一个与容器相关联的指针,然后重载各种运算操作来遍历,其中最重要的是*运算符与->运算符,以及++、–等可能需要重载的运算符重载。这和C++中的智能指针很像,智能指针也是将一个指针封装,然后通过引用计数或是其他方法完成自动释放内存的功能。
3.最常用的迭代器的相应型别有五种:value type、difference type、pointer、reference、iterator catagoly;

## 117.set与hash_set的区别

1.set底层是以RB-Tree实现,hash_set底层是以hash_table实现的;
2.RB-Tree有自动排序功能,而hash_table不具有自动排序功能;
3.set和hash_set元素的键值就是实值;
4.hash_table有一些无法处理的型别;

## 118.hashmap与map的区别

1.底层实现不同;
2.map具有自动排序的功能,hash_map不具有自动排序的功能;
3.hashtable有一些无法处理的型别;

## 119.map、set是怎么实现的,红黑树是怎么能够同时实现这两种容器? 为什么使用红黑树?

1)他们的底层都是以红黑树的结构实现,因此插入删除等操作都在O(logn)时间内完成,因此可以完成高效的插入删除;
2)在这里我们定义了一个模版参数,如果它是key那么它就是set,如果它是map,那么它就是map;底层是红黑树,实现map的红黑树的节点数据类型是key+value,而实现set的节点数据类型是value
3)因为map和set要求是自动排序的,红黑树能够实现这一功能,而且时间复杂度比较低。

## 120.如何在共享内存上使用stl标准库?

1)想像一下把STL容器,例如map, vector, list等等,放入共享内存中,IPC一旦有了这些强大的通用数据结构做辅助,无疑进程间通信的能力一下子强大了很多。我们没必要再为共享内存设计其他额外的数据结构,另外,STL的高度可扩展性将为IPC所驱使。STL容器被良好的封装,默认情况下有它们自己的内存管理方案。当一个元素被插入到一个STL列表(list)中时,列表容器自动为其分配内存,保存数据。考虑到要将STL容器放到共享内存中,而容器却自己在堆上分配内存。一个最笨拙的办法是在堆上构造STL容器,然后把容器复制到共享内存,并且确保所有容器的内部分配的内存指向共享内存中的相应区域,这基本是个不可能完成的任务。

2)假设进程A在共享内存中放入了数个容器,进程B如何找到这些容器呢?一个方法就是进程A把容器放在共享内存中的确定地址上(fixed offsets),则进程B可以从该已知地址上获取容器。另外一个改进点的办法是,进程A先在共享内存某块确定地址上放置一个map容器,然后进程A再创建其他容器,然后给其取个名字和地址一并保存到这个map容器里。进程B知道如何获取该保存了地址映射的map容器,然后同样再根据名字取得其他容器的地址。

## 121.map插入方式有几种?

1)用insert函数插入pair数据,
mapStudent.insert(pair<int, string>(1, “student_one”));
2)用insert函数插入value_type数据
mapStudent.insert(map<int, string>::value_type (1, “student_one”));
3)在insert函数中使用make_pair()函数
mapStudent.insert(make_pair(1, “student_one”));
4)用数组方式插入数据
mapStudent[1] = “student_one”;

## 122.STL中unordered_map(hash_map)和map的区别,hash_map如何解决冲突以及扩容

1)unordered_map和map类似,都是存储的key-value的值,可以通过key快速索引到value。不同的是unordered_map不会根据key的大小进行排序,
2)存储时是根据key的hash值判断元素是否相同,即unordered_map内部元素是无序的,而map中的元素是按照二叉搜索树存储,进行中序遍历会得到有序遍历。
3)所以使用时map的key需要定义operator<。而unordered_map需要定义hash_value函数并且重载operator==。但是很多系统内置的数据类型都自带这些,
4)那么如果是自定义类型,那么就需要自己重载operator<或者hash_value()了。
5)如果需要内部元素自动排序,使用map,不需要排序使用unordered_map
6)unordered_map的底层实现是hash_table;
7)hash_map底层使用的是hash_table,而hash_table使用的开链法进行冲突避免,所有hash_map采用开链法进行冲突解决。
8)什么时候扩容:当向容器添加元素的时候,会判断当前容器的元素个数,如果大于等于阈值—即当前数组的长度乘以加载因子的值的时候,就要自动扩容啦。
9)扩容(resize)就是重新计算容量,向HashMap对象里不停的添加元素,而HashMap对象内部的数组无法装载更多的元素时,对象就需要扩大数组的长度,以便能装入更多的元素。

## 123.vector越界访问下标,map越界访问下标?vector删除元素时会不会释放空间?

1)通过下标访问vector中的元素时不会做边界检查,即便下标越界。也就是说,下标与first迭代器相加的结果超过了finish迭代器的位置,程序也不会报错,而是返回这个地址中存储的值。如果想在访问vector中的元素时首先进行边界检查,可以使用vector中的at函数。通过使用at函数不但可以通过下标访问vector中的元素,而且在at函数内部会对下标进行边界检查。
2)map的下标运算符[]的作用是:将key作为下标去执行查找,并返回相应的值;如果不存在这个key,就将一个具有该key和value的某人值插入这个map。
3)erase()函数,只能删除内容,不能改变容量大小; erase成员函数,它删除了itVect迭代器指向的元素,并且返回要被删除的itVect之后的迭代器,迭代器相当于一个智能指针;clear()函数,只能清空内容,不能改变容量大小;如果要想在删除内容的同时释放内存,那么你可以选择deque容器。

## 124.map[]与find的区别?

1)map的下标运算符[]的作用是:将关键码作为下标去执行查找,并返回对应的值;如果不存在这个关键码,就将一个具有该关键码和值类型的默认值的项插入这个map。
2)map的find函数:用关键码执行查找,找到了返回该位置的迭代器;如果不存在这个关键码,就返回尾迭代器。

## 125.STL中list与queue之间的区别

1)list不再能够像vector一样以普通指针作为迭代器,因为其节点不保证在存储空间中连续存在;
2)list插入操作和结合才做都不会造成原有的list迭代器失效;
3)list不仅是一个双向链表,而且还是一个环状双向链表,所以它只需要一个指针;
4)list不像vector那样有可能在空间不足时做重新配置、数据移动的操作,所以插入前的所有迭代器在插入操作之后都仍然有效;
5)deque是一种双向开口的连续线性空间,所谓双向开口,意思是可以在头尾两端分别做元素的插入和删除操作;可以在头尾两端分别做元素的插入和删除操作;
6)deque和vector最大的差异,一在于deque允许常数时间内对起头端进行元素的插入或移除操作,二在于deque没有所谓容量概念,因为它是动态地以分段连续空间组合而成,随时可以增加一段新的空间并链接起来,deque没有所谓的空间保留功能。

## 126.STL中的allocator,deallocator

1)第一级配置器直接使用malloc()、free()和relloc(),第二级配置器视情况采用不同的策略:当配置区块超过128bytes时,视之为足够大,便调用第一级配置器;当配置器区块小于128bytes时,为了降低额外负担,使用复杂的内存池整理方式,而不再用一级配置器;
2)第二级配置器主动将任何小额区块的内存需求量上调至8的倍数,并维护16个free-list,各自管理大小为8~128bytes的小额区块;
3)空间配置函数allocate(),首先判断区块大小,大于128就直接调用第一级配置器,小于128时就检查对应的free-list。如果free-list之内有可用区块,就直接拿来用,如果没有可用区块,就将区块大小调整至8的倍数,然后调用refill(),为free-list重新分配空间;
4)空间释放函数deallocate(),该函数首先判断区块大小,大于128bytes时,直接调用一级配置器,小于128bytes就找到对应的free-list然后释放内存。

## 127.STL中hash_map扩容发生什么?

1)hash table表格内的元素称为桶(bucket),而由桶所链接的元素称为节点(node),其中存入桶元素的容器为stl本身很重要的一种序列式容器——vector容器。之所以选择vector为存放桶元素的基础容器,主要是因为vector容器本身具有动态扩容能力,无需人工干预。
2)向前操作:首先尝试从目前所指的节点出发,前进一个位置(节点),由于节点被安置于list内,所以利用节点的next指针即可轻易完成前进操作,如果目前正巧是list的尾端,就跳至下一个bucket身上,那正是指向下一个list的头部节点。

## 128.map如何创建?

1.vector 底层数据结构为数组 ,支持快速随机访问
2.list 底层数据结构为双向链表,支持快速增删
3.deque 底层数据结构为一个中央控制器和多个缓冲区,详细见STL源码剖析P146,支持首尾(中间不能)快速增删,也支持随机访问
deque是一个双端队列(double-ended queue),也是在堆中保存内容的.它的保存形式如下:
[堆1] --> [堆2] -->[堆3] --> …
每个堆保存好几个元素,然后堆和堆之间有指针指向,看起来像是list和vector的结合品.
4.stack 底层一般用list或deque实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时
5.queue 底层一般用list或deque实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时(stack和queue其实是适配器,而不叫容器,因为是对容器的再封装)
6.priority_queue 的底层数据结构一般为vector为底层容器,堆heap为处理规则来管理底层容器实现
7.set 底层数据结构为红黑树,有序,不重复
8.multiset 底层数据结构为红黑树,有序,可重复
9.map 底层数据结构为红黑树,有序,不重复
10.multimap 底层数据结构为红黑树,有序,可重复
11.hash_set 底层数据结构为hash表,无序,不重复
12.hash_multiset 底层数据结构为hash表,无序,可重复
13.hash_map 底层数据结构为hash表,无序,不重复
14.hash_multimap 底层数据结构为hash表,无序,可重复

## 129.vector的增加删除都是怎么做的?为什么是1.5倍?

1)新增元素:vector通过一个连续的数组存放元素,如果集合已满,在新增数据的时候,就要分配一块更大的内存,将原来的数据复制过来,释放之前的内存,在插入新增的元素;
2)对vector的任何操作,一旦引起空间重新配置,指向原vector的所有迭代器就都失效了 ;
3)初始时刻vector的capacity为0,塞入第一个元素后capacity增加为1;
4)不同的编译器实现的扩容方式不一样,VS2015中以1.5倍扩容,GCC以2倍扩容。

对比可以发现采用采用成倍方式扩容,可以保证常数的时间复杂度,而增加指定大小的容量只能达到O(n)的时间复杂度,因此,使用成倍的方式扩容。
1)考虑可能产生的堆空间浪费,成倍增长倍数不能太大,使用较为广泛的扩容方式有两种,以2二倍的方式扩容,或者以1.5倍的方式扩容。
2)以2倍的方式扩容,导致下一次申请的内存必然大于之前分配内存的总和,导致之前分配的内存不能再被使用,所以最好倍增长因子设置为(1,2)之间:
3)向量容器vector的成员函数pop_back()可以删除最后一个元素.
4)而函数erase()可以删除由一个iterator指出的元素,也可以删除一个指定范围的元素。
5)还可以采用通用算法remove()来删除vector容器中的元素.
6)不同的是:采用remove一般情况下不会改变容器的大小,而pop_back()与erase()等成员函数会改变容器的大小。

## 130.函数指针?

1)什么是函数指针?
函数指针指向的是特殊的数据类型,函数的类型是由其返回的数据类型和其参数列表共同决定的,而函数的名称则不是其类型的一部分。
一个具体函数的名字,如果后面不跟调用符号(即括号),则该名字就是该函数的指针(注意:大部分情况下,可以这么认为,但这种说法并不很严格)。
2)函数指针的声明方法
int (pf)(const int&, const int&); (1)
上面的pf就是一个函数指针,指向所有返回类型为int,并带有两个const int&参数的函数。注意pf两边的括号是必须的,否则上面的定义就变成了:
int *pf(const int&, const int&); (2)
而这声明了一个函数pf,其返回类型为int *, 带有两个const int&参数。
3)为什么有函数指针
函数与数据项相似,函数也有地址。我们希望在同一个函数中通过使用相同的形参在不同的时间使用产生不同的效果。
4)一个函数名就是一个指针,它指向函数的代码。一个函数地址是该函数的进入点,也就是调用函数的地址。函数的调用可以通过函数名,也可以通过指向函数的指针来调用。函数指针还允许将函数作为变元传递给其他函数;
5)两种方法赋值:
指针名 = 函数名; 指针名 = &函数名

## 131.说说你对c和c++的看法,c和c++的区别?

1)第一点就应该想到C是面向过程的语言,而C++是面向对象的语言,一般简历上第一条都是熟悉C/C++基本语法,了解C++面向对象思想,那么,请问什么是面向对象?
2)C和C++动态管理内存的方法不一样,C是使用malloc/free函数,而C++除此之外还有new/delete关键字;(关于malooc/free与new/delete的不同又可以说一大堆,最后的扩展_1部分列出十大区别);
3)接下来就不得不谈到C中的struct和C++的类,C++的类是C所没有的,但是C中的struct是可以在C++中正常使用的,并且C++对struct进行了进一步的扩展,使struct在C++中可以和class一样当做类使用,而唯一和class不同的地方在于struct的成员默认访问修饰符是public,而class默认的是private;
4)C++支持函数重载,而C不支持函数重载,而C++支持重载的依仗就在于C++的名字修饰与C不同,例如在C++中函数int fun(int ,int)经过名字修饰之后变为 _fun_int_int ,而C是
_fun,一般是这样的,所以C++才会支持不同的参数调用不同的函数;
5)C++中有引用,而C没有;这样就不得不提一下引用和指针的区别(文后扩展_2);
6)当然还有C++全部变量的默认链接属性是外链接,而C是内连接;
7)C 中用const修饰的变量不可以用在定义数组时的大小,但是C++用const修饰的变量可以(如果不进行&,解引用的操作的话,是存放在符号表的,不开辟内存);
8)当然还有局部变量的声明规则不同,多态,C++特有输入输出流之类的,很多,下面就不再列出来了; “`

## 132.c/c++的内存分配,详细说一下栈、堆、静态存储区?

1、栈区(stack)— 由编译器自动分配释放,存放函数的参数值,局部变量的值等
其操作方式类似于数据结构中的栈。
2、堆区(heap) — 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS(操作系统)回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放。
4、文字常量区 —常量字符串就是放在这里的。程序结束后由系统释放。
5、程序代码区 —存放函数体的二进制代码。

## 133.堆与栈的区别?

1)管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生memory leak。
2)空间大小:一般来讲在32位系统下,堆内存可以达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲,一般都是有一定的空间大小的,例如,在VC6下面,默认的栈空间大小是1M(好像是,记不清楚了)。当然,我们可以修改: 打开工程,依次操作菜单如下:Project->Setting->Link,在Category 中选中Output,然后在Reserve中设定堆栈的最大值和commit。 注意:reserve最小值为4Byte;commit是保留在虚拟内存的页文件里面,它设置的较大会使栈开辟较大的值,可能增加内存的开销和启动时间。
3)碎片问题:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在他弹出之前,在他上面的后进的栈内容已经被弹出,详细的可以参考数据结构,这里我们就不再一一讨论了。
4)生长方向:对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。
5)分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,它的动态分配是由编译器进行释放,无需我们手工实现。
6)分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。

## 134.野指针是什么?如何检测内存泄漏?

1)野指针:指向内存被释放的内存或者没有访问权限的内存的指针。
2)“野指针”的成因主要有3种:
①指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。例如
char *p = NULL;
char *str = new char(100);
②指针p被free或者delete之后,没有置为NULL;
③指针操作超越了变量的作用范围。
3)如何避免野指针:
①对指针进行初始化
①将指针初始化为NULL。
char * p = NULL;
②用malloc分配内存
char * p = (char * )malloc(sizeof(char));
③用已有合法的可访问的内存地址对指针初始化
char num[ 30] = {0};
char *p = num;
②指针用完后释放内存,将指针赋NULL。
delete§;
p = NULL;

## 135.悬空指针和野指针有什么区别?

1)野指针:野指针指,访问一个已删除或访问受限的内存区域的指针,野指针不能判断是否为NULL来避免。指针没有初始化,释放后没有置空,越界

2)悬空指针:一个指针的指向对象已被删除,那么就成了悬空指针。野指针是那些未初始化的指针。

## 136.内存泄漏

1)内存泄漏
内存泄漏是指由于疏忽或错误造成了程序未能释放掉不再使用的内存的情况。内存泄漏并非指内存在物理上消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制;
2)后果
只发生一次小的内存泄漏可能不被注意,但泄漏大量内存的程序将会出现各种证照:性能下降到内存逐渐用完,导致另一个程序失败;
3)如何排除
使用工具软件BoundsChecker,BoundsChecker是一个运行时错误检测工具,它主要定位程序运行时期发生的各种错误;
调试运行DEBUG版程序,运用以下技术:CRT(C run-time libraries)、运行时函数调用堆栈、内存泄漏时提示的内存分配序号(集成开发环境OUTPUT窗口),综合分析内存泄漏的原因,排除内存泄漏。
4)解决方法
智能指针。
5)检查、定位内存泄漏
检查方法:在main函数最后面一行,加上一句_CrtDumpMemoryLeaks()。调试程序,自然关闭程序让其退出,查看输出:
输出这样的格式{453}normal block at 0x02432CA8,868 bytes long
被{}包围的453就是我们需要的内存泄漏定位值,868 bytes long就是说这个地方有868比特内存没有释放。
定位代码位置
在main函数第一行加上_CrtSetBreakAlloc(453);意思就是在申请453这块内存的位置中断。然后调试程序,程序中断了,查看调用堆栈。加上头文件#include <crtdbg.h>

## 137.new和malloc的区别?

1、new/delete是C++关键字,需要编译器支持。malloc/free是库函数,需要头文件支持;
2、使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算。而malloc则需要显式地指出所需内存的尺寸。
3、new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。
4、new内存分配失败时,会抛出bac_alloc异常。malloc分配内存失败时返回NULL。
5、new会先调用operator new函数,申请足够的内存(通常底层使用malloc实现)。然后调用类型的构造函数,初始化成员变量,最后返回自定义类型指针。delete先调用析构函数,然后调用operator delete函数释放内存(通常底层使用free实现)。malloc/free是库函数,只能动态的申请和释放内存,无法强制要求其做自定义类型对象构造和析构工作。

## 138.delete p;与delete[]p,allocator

1、动态数组管理new一个数组时,[]中必须是一个整数,但是不一定是常量整数,普通数组必须是一个常量整数;
2、new动态数组返回的并不是数组类型,而是一个元素类型的指针;
3、delete[]时,数组中的元素按逆序的顺序进行销毁;
4、new在内存分配上面有一些局限性,new的机制是将内存分配和对象构造组合在一起,同样的,delete也是将对象析构和内存释放组合在一起的。allocator将这两部分分开进行,allocator申请一部分内存,不进行初始化对象,只有当需要的时候才进行初始化操作。

## 139.new和delete的实现原理, delete是如何知道释放内存的大小的额?

1、new简单类型直接调用operator new分配内存;而对于复杂结构,先调用operator new分配内存,然后在分配的内存上调用构造函数;对于简单类型,new[]计算好大小后调用operator new;对于复杂数据结构,new[]先调用operator new[]分配内存,然后在p的前四个字节写入数组大小n,然后调用n次构造函数,针对复杂类型,new[]会额外存储数组大小;
①new表达式调用一个名为operator new(operator new[])函数,分配一块足够大的、原始的、未命名的内存空间;
②编译器运行相应的构造函数以构造这些对象,并为其传入初始值;
③对象被分配了空间并构造完成,返回一个指向该对象的指针。
2、delete简单数据类型默认只是调用free函数;复杂数据类型先调用析构函数再调用operator delete;针对简单类型,delete和delete[]等同。假设指针p指向new[]分配的内存。因为要4字节存储数组大小,实际分配的内存地址为[p-4],系统记录的也是这个地址。delete[]实际释放的就是p-4指向的内存。而delete会直接释放p指向的内存,这个内存根本没有被系统记录,所以会崩溃。
3、需要在 new [] 一个对象数组时,需要保存数组的维度,C++ 的做法是在分配数组空间时多分配了 4 个字节的大小,专门保存数组的大小,在 delete [] 时就可以取出这个保存的数,就知道了需要调用析构函数多少次了。

## 140.malloc申请的存储空间能用delete释放吗

不能,malloc /free主要为了兼容C,new和delete 完全可以取代malloc /free的。malloc /free的操作对象都是必须明确大小的。而且不能用在动态类上。new 和delete会自动进行类型检查和大小,malloc/free不能执行构造函数与析构函数,所以动态对象它是不行的。当然从理论上说使用malloc申请的内存是可以通过delete释放的。不过一般不这样写的。而且也不能保证每个C++的运行时都能正常。

## 141.malloc与free的实现原理?

1、在标准C库中,提供了malloc/free函数分配释放内存,这两个函数底层是由brk、mmap、,munmap这些系统调用实现的;
2、brk是将数据段(.data)的最高地址指针_edata往高地址推,mmap是在进程的虚拟地址空间中(堆和栈中间,称为文件映射区域的地方)找一块空闲的虚拟内存。这两种方式分配的都是虚拟内存,没有分配物理内存。在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系;
3、malloc小于128k的内存,使用brk分配内存,将_edata往高地址推;malloc大于128k的内存,使用mmap分配内存,在堆和栈之间找一块空闲内存分配;brk分配的内存需要等到高地址内存释放以后才能释放,而mmap分配的内存可以单独释放。当最高地址空间的空闲内存超过128K(可由M_TRIM_THRESHOLD选项调节)时,执行内存紧缩操作(trim)。在上一个步骤free的时候,发现最高地址空闲内存超过128K,于是内存紧缩。
4、malloc是从堆里面申请内存,也就是说函数返回的指针是指向堆里面的一块内存。操作系统中有一个记录空闲内存地址的链表。当操作系统收到程序的申请时,就会遍历该链表,然后就寻找第一个空间大于所申请空间的堆结点,然后就将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。

## 142.malloc、realloc、calloc的区别

1)malloc函数
void* malloc(unsigned int num_size);
int p = malloc(20sizeof(int));申请20个int类型的空间;
2)calloc函数
void* calloc(size_t n,size_t size);
int *p = calloc(20, sizeof(int));
省去了人为空间计算;malloc申请的空间的值是随机初始化的,calloc申请的空间的值是初始化为0的;
3)realloc函数
void realloc(void *p, size_t new_size);
给动态分配的空间分配额外的空间,用于扩充容量。

## 143.__stdcall和__cdecl的区别?

1)__stdcall
__stdcall是函数恢复堆栈,只有在函数代码的结尾出现一次恢复堆栈的代码;在编译时就规定了参数个数,无法实现不定个数的参数调用;
2)__cdecl
__cdecl是调用者恢复堆栈,假设有100个函数调用函数a,那么内存中就有100端恢复堆栈的代码;可以不定参数个数;每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用__stacall函数大。

## 144.使用智能指针管理内存资源,RAII

1)RAII全称是“Resource Acquisition is Initialization”,直译过来是“资源获取即初始化”,也就是说在构造函数中申请分配资源,在析构函数中释放资源。因为C++的语言机制保证了,当一个对象创建的时候,自动调用构造函数,当对象超出作用域的时候会自动调用析构函数。所以,在RAII的指导下,我们应该使用类来管理资源,将资源和对象的生命周期绑定。
2)智能指针(std::shared_ptr和std::unique_ptr)即RAII最具代表的实现,使用智能指针,可以实现自动的内存管理,再也不需要担心忘记delete造成的内存泄漏。毫不夸张的来讲,有了智能指针,代码中几乎不需要再出现delete了。

## 145.手写实现智能指针类

1)智能指针是一个数据类型,一般用模板实现,模拟指针行为的同时还提供自动垃圾回收机制。它会自动记录SmartPointer<T*>对象的引用计数,一旦T类型对象的引用计数为0,就释放该对象。除了指针对象外,我们还需要一个引用计数的指针设定对象的值,并将引用计数计为1,需要一个构造函数。新增对象还需要一个构造函数,析构函数负责引用计数减少和释放内存。通过覆写赋值运算符,才能将一个旧的智能指针赋值给另一个指针,同时旧的引用计数减1,新的引用计数加1
2)一个构造函数、拷贝构造函数、复制构造函数、析构函数、移走函数;

## 146.内存对齐?位域?

1、 分配内存的顺序是按照声明的顺序。
2、 每个变量相对于起始位置的偏移量必须是该变量类型大小的整数倍,不是整数倍空出内存,直到偏移量是整数倍为止。
3、 最后整个结构体的大小必须是里面变量类型最大值的整数倍。

添加了#pragma pack(n)后规则就变成了下面这样:
1、 偏移量要是n和当前变量大小中较小值的整数倍
2、 整体大小要是n和最大变量大小中较小值的整数倍
3、 n值必须为1,2,4,8…,为其他值时就按照默认的分配规则

## 147.结构体变量比较是否相等

1)重载了 “” 操作符

```
struct foo {
int a;
int b;
bool operator(const foo& rhs) // 操作运算符重载
{
return( a == rhs.a) && (b == rhs.b);

}
1
};
```

2)元素的话,一个个比;
3)指针直接比较,如果保存的是同一个实例地址,则(p1==p2)为真;

## 148.位运算

若一个数m满足 m = 2^n;那么k%m=k&(m-1)

## 149.为什么内存对齐

1、平台原因(移植原因)
1)不是所有的硬件平台都能访问任意地址上的任意数据的;
2)某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异
2、性能原因:
1)数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
2)原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

## 150.函数调用过程栈的变化,返回值和参数变量哪个先入栈?

1、调用者函数把被调函数所需要的参数按照与被调函数的形参顺序相反的顺序压入栈中,即:从右向左依次把被调函数所需要的参数压入栈;
2、调用者函数使用call指令调用被调函数,并把call指令的下一条指令的地址当成返回地址压入栈中(这个压栈操作隐含在call指令中);
3、在被调函数中,被调函数会先保存调用者函数的栈底地址(push ebp),然后再保存调用者函数的栈顶地址,即:当前被调函数的栈底地址(mov ebp,esp);
4、在被调函数中,从ebp的位置处开始存放被调函数中的局部变量和临时变量,并且这些变量的地址按照定义时的顺序依次减小,即:这些变量的地址是按照栈的延伸方向排列的,先定义的变量先入栈,后定义的变量后入栈;

## 151.怎样判断两个浮点数是否相等?

对两个浮点数判断大小和是否相等不能直接用==来判断,会出错!明明相等的两个数比较反而是不相等!对于两个浮点数比较只能通过相减并与预先设定的精度比较,记得要取绝对值!浮点数与0的比较也应该注意。与浮点数的表示方式有关。

## 152.宏定义一个取两个数中较大值的功能

#define MAX(x,y)((x>y?)x:y)

## 153.define、const、typedef、inline使用方法?

一、const与#define的区别:
1)const定义的常量是变量带类型,而#define定义的只是个常数不带类型;
2)define只在预处理阶段起作用,简单的文本替换,而const在编译、链接过程中起作用;
3)define只是简单的字符串替换没有类型检查。而const是有数据类型的,是要进行判断的,可以避免一些低级错误;
4)define预处理后,占用代码段空间,const占用数据段空间;
5)const不能重定义,而define可以通过#undef取消某个符号的定义,进行重定义;
6)define独特功能,比如可以用来防止文件重复引用。
二、#define和别名typedef的区别
1)执行时间不同,typedef在编译阶段有效,typedef有类型检查的功能;#define是宏定义,发生在预处理阶段,不进行类型检查;
2)功能差异,typedef用来定义类型的别名,定义与平台无关的数据类型,与struct的结合使用等。#define不只是可以为类型取别名,还可以定义常量、变量、编译开关等。
3)作用域不同,#define没有作用域的限制,只要是之前预定义过的宏,在以后的程序中都可以使用。而typedef有自己的作用域。
三、define与inline的区别
1)#define是关键字,inline是函数;
2)宏定义在预处理阶段进行文本替换,inline函数在编译阶段进行替换;
3)inline函数有类型检查,相比宏定义比较安全;

## 154.printf实现原理?

在C/C++中,对函数参数的扫描是从后向前的。C/C++的函数参数是通过压入堆栈的方式来给函数传参数的(堆栈是一种先进后出的数据结构),最先压入的参数最后出来,在计算机的内存中,数据有2块,一块是堆,一块是栈(函数参数及局部变量在这里),而栈是从内存的高地址向低地址生长的,控制生长的就是堆栈指针了,最先压入的参数是在最上面,就是说在所有参数的最后面,最后压入的参数在最下面,结构上看起来是第一个,所以最后压入的参数总是能够被函数找到,因为它就在堆栈指针的上方。printf的第一个被找到的参数就是那个字符指针,就是被双引号括起来的那一部分,函数通过判断字符串里控制参数的个数来判断参数个数及数据类型,通过这些就可算出数据需要的堆栈指针的偏移量了,下面给出printf(“%d,%d”,a,b);(其中a、b都是int型的)的汇编代码.

## 155.#include 的顺序以及尖叫括号和双引号的区别

表示编译器只在系统默认目录或尖括号内的工作目录下搜索头文件,并不去用户的工作目录下寻找,所以一般尖括号用于包含标准库文件;
表示编译器先在用户的工作目录下搜索头文件,如果搜索不到则到系统默认目录下去寻找,所以双引号一般用于包含用户自己编写的头文件。

## 156.lambda函数

1)利用lambda表达式可以编写内嵌的匿名函数,用以替换独立函数或者函数对象;
2)每当你定义一个lambda表达式后,编译器会自动生成一个匿名类(这个类当然重载了()运算符),我们称为闭包类型(closure type)。那么在运行时,这个lambda表达式就会返回一个匿名的闭包实例,其实一个右值。所以,我们上面的lambda表达式的结果就是一个个闭包。闭包的一个强大之处是其可以通过传值或者引用的方式捕捉其封装作用域内的变量,前面的方括号就是用来定义捕捉模式以及变量,我们又将其称为lambda捕捉块。
3)lambda表达式的语法定义如下:
[capture] (parameters) mutable ->return-type {statement};
4)lambda必须使用尾置返回来指定返回类型,可以忽略参数列表和返回值,但必须永远包含捕获列表和函数体;

## 157.hello world 程序开始到打印到屏幕上的全过程?

1.用户告诉操作系统执行HelloWorld程序(通过键盘输入等)
2.操作系统:找到helloworld程序的相关信息,检查其类型是否是可执行文件;并通过程序首部信息,确定代码和数据在可执行文件中的位置并计算出对应的磁盘块地址。
3.操作系统:创建一个新进程,将HelloWorld可执行文件映射到该进程结构,表示由该进程执行helloworld程序。
4.操作系统:为helloworld程序设置cpu上下文环境,并跳到程序开始处。
5.执行helloworld程序的第一条指令,发生缺页异常
6.操作系统:分配一页物理内存,并将代码从磁盘读入内存,然后继续执行helloworld程序
7.helloword程序执行puts函数(系统调用),在显示器上写一字符串
8.操作系统:找到要将字符串送往的显示设备,通常设备是由一个进程控制的,所以,操作系统将要写的字符串送给该进程
9.操作系统:控制设备的进程告诉设备的窗口系统,它要显示该字符串,窗口系统确定这是一个合法的操作,然后将字符串转换成像素,将像素写入设备的存储映像区
10.视频硬件将像素转换成显示器可接收和一组控制数据信号
11.显示器解释信号,激发液晶屏
12.OK,我们在屏幕上看到了HelloWorld

## 158.模板类和模板函数的区别是什么?

函数模板的实例化是由编译程序在处理函数调用时自动完成的,而类模板的实例化必须由程序员在程序中显式地指定。即函数模板允许隐式调用和显式调用而类模板只能显示调用。在使用时类模板必须加,而函数模板不必

## 159.为什么模板类一般都是放在一个h文件中

1)模板定义很特殊。由template<…>处理的任何东西都意味着编译器在当时不为它分配存储空间,它一直处于等待状态直到被一个模板实例告知。在编译器和连接器的某一处,有一机制能去掉指定模板的多重定义。所以为了容易使用,几乎总是在头文件中放置全部的模板声明和定义。
2)在分离式编译的环境下,编译器编译某一个.cpp文件时并不知道另一个.cpp文件的存在,也不会去查找(当遇到未决符号时它会寄希望于连接器)。这种模式在没有模板的情况下运行良好,但遇到模板时就傻眼了,因为模板仅在需要的时候才会实例化出来,所以,当编译器只看到模板的声明时,它不能实例化该模板,只能创建一个具有外部连接的符号并期待连接器能够将符号的地址决议出来。然而当实现该模板的.cpp文件中没有用到模板的实例时,编译器懒得去实例化,所以,整个工程的.obj中就找不到一行模板实例的二进制代码,于是连接器也黔驴技穷了。

## 160.C++中类成员的访问权限和继承权限问题。

1)三种访问权限
①public:用该关键字修饰的成员表示公有成员,该成员不仅可以在类内可以被 访问,在类外也是可以被访问的,是类对外提供的可访问接口;
② private:用该关键字修饰的成员表示私有成员,该成员仅在类内可以被访问,在类体外是隐藏状态;
③ protected:用该关键字修饰的成员表示保护成员,保护成员在类体外同样是隐藏状态,但是对于该类的派生类来说,相当于公有成员,在派生类中可以被访问。
2)三种继承方式
①若继承方式是public,基类成员在派生类中的访问权限保持不变,也就是说,基类中的成员访问权限,在派生类中仍然保持原来的访问权限;
② 若继承方式是private,基类所有成员在派生类中的访问权限都会变为私有(private)权限;
③若继承方式是protected,基类的共有成员和保护成员在派生类中的访问权限都会变为保护(protected)权限,私有成员在派生类中的访问权限仍然是私有(private)权限。

## 161.cout和printf有什么区别?

cout<<是一个函数,cout<<后可以跟不同的类型是因为cout<<已存在针对各种类型数据的重载,所以会自动识别数据的类型。输出过程会首先将输出字符放入缓冲区,然后输出到屏幕。
cout是有缓冲输出:
cout < < "abc " < <endl;
或cout < < "abc\n ";cout < <flush; 这两个才是一样的.
endl相当于输出回车后,再强迫缓冲输出。
flush立即强迫缓冲输出。
printf是无缓冲输出。有输出时立即输出

## 162.重载运算符?

1、我们只能重载已有的运算符,而无权发明新的运算符;对于一个重载的运算符,其优先级和结合律与内置类型一致才可以;不能改变运算符操作数个数;
2、. :: ?: sizeof typeid **不能重载;
3、两种重载方式,成员运算符和非成员运算符,成员运算符比非成员运算符少一个参数;下标运算符、箭头运算符必须是成员运算符;
4、引入运算符重载,是为了实现类的多态性;
5、当重载的运算符是成员函数时,this绑定到左侧运算符对象。成员运算符函数的参数数量比运算符对象的数量少一个;至少含有一个类类型的参数;
6、从参数的个数推断到底定义的是哪种运算符,当运算符既是一元运算符又是二元运算符(+,-,*,&);
7、下标运算符必须是成员函数,下标运算符通常以所访问元素的引用作为返回值,同时最好定义下标运算符的常量版本和非常量版本;
8、箭头运算符必须是类的成员,解引用通常也是类的成员;重载的箭头运算符必须返回类的指针;

## 163.函数重载函数匹配原则

1)名字查找
2)确定候选函数
3)寻找最佳匹配

## 164.定义和声明的区别

1.如果是指变量的声明和定义
从编译原理上来说,声明是仅仅告诉编译器,有个某类型的变量会被使用,但是编译器并不会为它分配任何内存。而定义就是分配了内存。
2.如果是指函数的声明和定义
声明:一般在头文件里,对编译器说:这里我有一个函数叫function() 让编译器知道这个函数的存在。
定义:一般在源文件里,具体就是函数的实现过程 写明函数体。

## 165.C++类型转换有四种

1)static_cast能进行基础类型之间的转换,也是最常看到的类型转换。它主要有如下几种用法:
1 . 用于类层次结构中父类和子类之间指针或引用的转换。进行上行转换(把子类的指针或引用转换成父类表示)是安全的;
2 . 进行下行转换(把父类指针或引用转换成子类指针或引用)时,由于没有动态类型检查,所以是不安全的;
3 . 用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
4 . 把void指针转换成目标类型的指针(不安全!!)
5 . 把任何类型的表达式转换成void类型。
2)const_cast运算符用来修改类型的const或volatile属性。除了去掉const 或volatile修饰之外, type_id和expression得到的类型是一样的。但需要特别注意的是const_cast不是用于去除变量的常量性,而是去除指向常数对象的指针或引用的常量性,其去除常量性的对象必须为指针或引用。
3)reinterpret_cast它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,在把该整数转换成原类型的指针,还可以得到原先的指针值)。
4)dynamic_cast 主要用在继承体系中的安全向下转型。它能安全地将指向基类的指针转型为指向子类的指针或引用,并获知转型动作成功是否。转型失败会返回null(转型对象为指针时)或抛出异常bad_cast(转型对象为引用时)。 dynamic_cast 会动用运行时信息(RTTI)来进行类型安全检查,因此 dynamic_cast 存在一定的效率损失。当使用dynamic_cast时,该类型必须含有虚函数,这是因为dynamic_cast使用了存储在VTABLE中的信息来判断实际的类型,RTTI运行时类型识别用于判断类型。typeid表达式的形式是typeid(e),typeid操作的结果是一个常量对象的引用,该对象的类型是type_info或type_info的派生。

## 166.全局变量和static变量的区别

1、全局变量(外部变量)的说明之前再冠以static就构成了静态的全局变量。全局变量本身就是静态存储方式,静态全局变量当然也是静态存储方式。
这两者在存储方式上并无不同。这两者的区别在于非静态全局变量的作用域是整个源程序,当一个源程序由多个原文件组成时,非静态的全局变量在各个源文件中都是有效的。而静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其他源文件中引起错误。static全局变量与普通的全局变量的区别是static全局变量只初始化一次,防止在其他文件单元被引用。
2.static函数与普通函数有什么区别?
  static函数与普通的函数作用域不同。尽在本文件中。只在当前源文件中使用的函数应该说明为内部函数(static),内部函数应该在当前源文件中说明和定义。对于可在当前源文件以外使用的函数应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件。
static函数与普通函数最主要区别是static函数在内存中只有一份,普通静态函数在每个被调用中维持一份拷贝程序的局部变量存在于(堆栈)中,全局变量存在于(静态区)中,动态申请数据存在于(堆)

## 167.静态成员与普通成员的区别

1)生命周期
静态成员变量从类被加载开始到类被卸载,一直存在;
普通成员变量只有在类创建对象后才开始存在,对象结束,它的生命期结束;
2)共享方式
静态成员变量是全类共享;普通成员变量是每个对象单独享用的;
3)定义位置
普通成员变量存储在栈或堆中,而静态成员变量存储在静态全局区;
4)初始化位置
普通成员变量在类中初始化;静态成员变量在类外初始化;
5)默认实参
可以使用静态成员变量作为默认实参,

## 168.说一下理解 ifdef endif

1)一般情况下,源程序中所有的行都参加编译。但是有时希望对其中一部分内容只在满足一定条件才进行编译,也就是对一部分内容指定编译的条件,这就是“条件编译”。有时,希望当满足某条件时对一组语句进行编译,而当条件不满足时则编译另一组语句。
2)条件编译命令最常见的形式为:
#ifdef 标识符
程序段1
#else
程序段2
#endif
它的作用是:当标识符已经被定义过(一般是用#define命令定义),则对程序段1进行编译,否则编译程序段2。
其中#else部分也可以没有,即:
#ifdef
程序段1
#denif
3)在一个大的软件工程里面,可能会有多个文件同时包含一个头文件,当这些文件编译链接成一个可执行文件上时,就会出现大量“重定义”错误。在头文件中使用#define、#ifndef、#ifdef、#endif能避免头文件重定义。

## 169.隐式转换,如何消除隐式转换?

1.C++的基本类型中并非完全的对立,部分数据类型之间是可以进行隐式转换的。所谓隐式转换,是指不需要用户干预,编译器私下进行的类型转换行为。很多时候用户可能都不知道进行了哪些转换

2. C++面向对象的多态特性,就是通过父类的类型实现对子类的封装。通过隐式转换,你可以直接将一个子类的对象使用父类的类型进行返回。在比如,数值和布尔类型的转换,整数和浮点数的转换等。某些方面来说,隐式转换给C++程序开发者带来了不小的便捷。C++是一门强类型语言,类型的检查是非常严格的。
3.基本数据类型 基本数据类型的转换以取值范围的作为转换基础(保证精度不丢失)。隐式转换发生在从小->大的转换中。比如从char转换为int。从int->long。自定义对象 子类对象可以隐式的转换为父类对象。
4.C++中提供了explicit关键字,在构造函数声明的时候加上explicit关键字,能够禁止隐式转换。
5.如果构造函数只接受一个参数,则它实际上定义了转换为此类类型的隐式转换机制。可以通过将构造函数声明为explicit加以制止隐式类型转换,关键字explicit只对一个实参的构造函数有效,需要多个实参的构造函数不能用于执行隐式转换,所以无需将这些构造函数指定为explicit。

## 170.虚函数的内存结构,那菱形继承的虚函数内存结构呢

## 171.多继承的优缺点,作为一个开发者怎么看待多继承

1)C++允许为一个派生类指定多个基类,这样的继承结构被称做多重继承。
2)多重继承的优点很明显,就是对象可以调用多个基类中的接口;
3)如果派生类所继承的多个基类有相同的基类,而派生类对象需要调用这个祖先类的接口方法,就会容易出现二义性
4)加上全局符确定调用哪一份拷贝。比如pa.Author::eat()调用属于Author的拷贝。
5)使用虚拟继承,使得多重继承类Programmer_Author只拥有Person类的一份拷贝。

## 172.迭代器++it,it++哪个好,为什么

1)前置返回一个引用,后置返回一个对象
// ++i实现代码为:
int& operator++()
{
*this += 1;
return *this;
}

2)前置不会产生临时对象,后置必须产生临时对象,临时对象会导致效率降低
//i++实现代码为:
int operator++(int)
{
int temp = *this;
++*this;
return temp;
}

101.C++如何处理多个异常的?
1)C++中的异常情况:
语法错误(编译错误):比如变量未定义、括号不匹配、关键字拼写错误等等编译器在编译时能发现的错误,这类错误可以及时被编译器发现,而且可以及时知道出错的位置及原因,方便改正。
运行时错误:比如数组下标越界、系统内存不足等等。这类错误不易被程序员发现,它能通过编译且能进入运行,但运行时会出错,导致程序崩溃。为了有效处理程序运行时错误,C++中引入异常处理机制来解决此问题。
2)C++异常处理机制:
异常处理基本思想:执行一个函数的过程中发现异常,可以不用在本函数内立即进行处理, 而是抛出该异常,让函数的调用者直接或间接处理这个问题。
C++异常处理机制由3个模块组成:try(检查)、throw(抛出)、catch(捕获)
抛出异常的语句格式为:throw 表达式;如果try块中程序段发现了异常则抛出异常。
try
{
可能抛出异常的语句;(检查)
}
catch(类型名[形参名])//捕获特定类型的异常
{
//处理1;
}
catch(类型名[形参名])//捕获特定类型的异常
{
//处理2;
}
catch(…)//捕获所有类型的异常
{
}

## 173.模板和实现可不可以不写在一个文件里面?为什么?

因为在编译时模板并不能生成真正的二进制代码,而是在编译调用模板类或函数的CPP文件时才会去找对应的模板声明和实现,在这种情况下编译器是不知道实现模板类或函数的CPP文件的存在,所以它只能找到模板类或函数的声明而找不到实现,而只好创建一个符号寄希望于链接程序找地址。但模板类或函数的实现并不能被编译成二进制代码,结果链接程序找不到地址只好报错了。
《C++编程思想》第15章(第300页)说明了原因:模板定义很特殊。由template<…>处理的任何东西都意味着编译器在当时不为它分配存储空间,它一直处于等待状态直到被一个模板实例告知。在编译器和连接器的某一处,有一机制能去掉指定模板的多重定义。所以为了容易使用,几乎总是在头文件中放置全部的模板声明和定义。

## 174.在成员函数中调用delete this会出现什么问题?对象还可以使用吗?

1.在类对象的内存空间中,只有数据成员和虚函数表指针,并不包含代码内容,类的成员函数单独放在代码段中。在调用成员函数时,隐含传递一个this指针,让成员函数知道当前是哪个对象在调用它。当调用delete this时,类对象的内存空间被释放。在delete this之后进行的其他任何函数调用,只要不涉及到this指针的内容,都能够正常运行。一旦涉及到this指针,如操作数据成员,调用虚函数等,就会出现不可预期的问题。
2.为什么是不可预期的问题?
delete this之后不是释放了类对象的内存空间了么,那么这段内存应该已经还给系统,不再属于这个进程。照这个逻辑来看,应该发生指针错误,无访问权限之类的令系统崩溃的问题才对啊?这个问题牵涉到操作系统的内存管理策略。delete this释放了类对象的内存空间,但是内存空间却并不是马上被回收到系统中,可能是缓冲或者其他什么原因,导致这段内存空间暂时并没有被系统收回。此时这段内存是可以访问的,你可以加上100,加上200,但是其中的值却是不确定的。当你获取数据成员,可能得到的是一串很长的未初始化的随机数;访问虚函数表,指针无效的可能性非常高,造成系统崩溃。
3.如果在类的析构函数中调用delete this,会发生什么?
会导致堆栈溢出。原因很简单,delete的本质是“为将被释放的内存调用一个或多个析构函数,然后,释放内存”。显然,delete this会去调用本对象的析构函数,而析构函数中又调用delete this,形成无限递归,造成堆栈溢出,系统崩溃。

## 175.智能指针的作用;

1)C++11中引入了智能指针的概念,方便管理堆内存。使用普通指针,容易造成堆内存泄露(忘记释放),二次释放,程序发生异常时内存泄露等问题等,使用智能指针能更好的管理堆内存。
2)智能指针在C++11版本之后提供,包含在头文件中,shared_ptr、unique_ptr、weak_ptr。shared_ptr多个指针指向相同的对象。shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。每使用他一次,内部的引用计数加1,每析构一次,内部的引用计数减1,减为0时,自动删除所指向的堆内存。shared_ptr内部的引用计数是线程安全的,但是对象的读取需要加锁。
3)初始化。智能指针是个模板类,可以指定类型,传入指针通过构造函数初始化。也可以使用make_shared函数初始化。不能将指针直接赋值给一个智能指针,一个是类,一个是指针。例如std::shared_ptr p4 = new int(1);的写法是错误的
拷贝和赋值。拷贝使得对象的引用计数增加1,赋值使得原对象引用计数减1,当计数为0时,自动释放内存。后来指向的对象引用计数加1,指向后来的对象
4)unique_ptr“唯一”拥有其所指对象,同一时刻只能有一个unique_ptr指向给定对象(通过禁止拷贝语义、只有移动语义来实现)。相比与原始指针unique_ptr用于其RAII的特性,使得在出现异常的情况下,动态资源能得到释放。unique_ptr指针本身的生命周期:从unique_ptr指针创建时开始,直到离开作用域。离开作用域时,若其指向对象,则将其所指对象销毁(默认使用delete操作符,用户可指定其他操作)。unique_ptr指针与其所指对象的关系:在智能指针生命周期内,可以改变智能指针所指对象,如创建智能指针时通过构造函数指定、通过reset方法重新指定、通过release方法释放所有权、通过移动语义转移所有权。
5)智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。每次创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。
6)weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个强引用的 shared_ptr. weak_ptr只是提供了对管理对象的一个访问手段。weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少.

## 176.auto_ptr作用

1)auto_ptr的出现,主要是为了解决“有异常抛出时发生内存泄漏”的问题;抛出异常,将导致指针p所指向的空间得不到释放而导致内存泄漏;
2)auto_ptr构造时取得某个对象的控制权,在析构时释放该对象。我们实际上是创建一个auto_ptr类型的局部对象,该局部对象析构时,会将自身所拥有的指针空间释放,所以不会有内存泄漏;
3)auto_ptr的构造函数是explicit,阻止了一般指针隐式转换为 auto_ptr的构造,所以不能直接将一般类型的指针赋值给auto_ptr类型的对象,必须用auto_ptr的构造函数创建对象;
4)由于auto_ptr对象析构时会删除它所拥有的指针,所以使用时避免多个auto_ptr对象管理同一个指针;
5)Auto_ptr内部实现,析构函数中删除对象用的是delete而不是delete[],所以auto_ptr不能管理数组;
6)auto_ptr支持所拥有的指针类型之间的隐式类型转换。
7)可以通过和->运算符对auto_ptr所有用的指针进行提领操作;
8)T get(),获得auto_ptr所拥有的指针;T* release(),释放auto_ptr的所有权,并将所有用的指针返回。

## 177.class、union、struct的区别

1)C语言中,struct只是一个聚合数据类型,没有权限设置,无法添加成员函数,无法实现面向对象编程,且如果没有typedef结构名,声明结构变量必须添加关键字struct。
2)C++中,struct功能大大扩展,可以有权限设置(默认权限为public),可以像class一样有成员函数,继承(默认public继承),可以实现面对对象编程,允许在声明结构变量时省略关键字struct。
3)C与C++中的union:一种数据格式,能够存储不同的数据类型,但只能同时存储其中的一种类型。C++ union结构式一种特殊的类。它能够包含访问权限、成员变量、成员函数(可以包含构造函数和析构函数)。它不能包含虚函数和静态数据变量。它也不能被用作其他类的基类,它本身也不能有从某个基类派生而来。Union中得默认访问权限是public。union类型是共享内存的,以size最大的结构作为自己的大小。每个数据成员在内存中的起始地址是相同的。
4)在C/C++程序的编写中,当多个基本数据类型或复合数据结构要占用同一片内存时,我们要使用联合体;当多种类型,多个对象,多个事物只取其一时(我们姑且通俗地称其为“n 选1”),我们也可以使用联合体来发挥其长处。在某一时刻,一个union中只能有一个值是有效的。union的一个用法就是可以用来测试CPU是大端模式还是小端模式:

## 178.动态联编与静态联编

1)在C++中,联编是指一个计算机程序的不同部分彼此关联的过程。按照联编所进行的阶段不同,可以分为静态联编和动态联编;
2)静态联编是指联编工作在编译阶段完成的,这种联编过程是在程序运行之前完成的,又称为早期联编。要实现静态联编,在编译阶段就必须确定程序中的操作调用(如函数调用)与执行该操作代码间的关系,确定这种关系称为束定,在编译时的束定称为静态束定。静态联编对函数的选择是基于指向对象的指针或者引用的类型。其优点是效率高,但灵活性差。
3)动态联编是指联编在程序运行时动态地进行,根据当时的情况来确定调用哪个同名函数,实际上是在运行时虚函数的实现。这种联编又称为晚期联编,或动态束定。动态联编对成员函数的选择是基于对象的类型,针对不同的对象类型将做出不同的编译结果。C++中一般情况下的联编是静态联编,但是当涉及到多态性和虚函数时应该使用动态联编。动态联编的优点是灵活性强,但效率低。动态联编规定,只能通过指向基类的指针或基类对象的引用来调用虚函数,其格式为:指向基类的指针变量名->虚函数名(实参表)或基类对象的引用名.虚函数名(实参表)
4)实现动态联编三个条件:
必须把动态联编的行为定义为类的虚函数;
类之间应满足子类型关系,通常表现为一个类从另一个类公有派生而来;
必须先使用基类指针指向子类型的对象,然后直接或间接使用基类指针调用虚函数;

## 179.动态编译与静态编译

1)静态编译,编译器在编译可执行文件时,把需要用到的对应动态链接库中的部分提取出来,连接到可执行文件中去,使可执行文件在运行时不需要依赖于动态链接库;
2)动态编译的可执行文件需要附带一个动态链接库,在执行时,需要调用其对应动态链接库的命令。所以其优点一方面是缩小了执行文件本身的体积,另一方面是加快了编译速度,节省了系统资源。缺点是哪怕是很简单的程序,只用到了链接库的一两条命令,也需要附带一个相对庞大的链接库;二是如果其他计算机上没有安装对应的运行库,则用动态编译的可执行文件就不能运行。

## 180.动态链接和静态链接区别

1)静态连接库就是把(lib)文件中用到的函数代码直接链接进目标程序,程序运行的时候不再需要其它的库文件;动态链接就是把调用的函数所在文件模块(DLL)和调用函数在文件中的位置等信息链接进目标程序,程序运行的时候再从DLL中寻找相应函数代码,因此需要相应DLL文件的支持。
2)静态链接库与动态链接库都是共享代码的方式,如果采用静态链接库,则无论你愿不愿意,lib 中的指令都全部被直接包含在最终生成的 EXE 文件中了。但是若使用 DLL,该 DLL 不必被包含在最终 EXE 文件中,EXE 文件执行时可以“动态”地引用和卸载这个与 EXE 独立的 DLL 文件。静态链接库和动态链接库的另外一个区别在于静态链接库中不能再包含其他的动态链接库或者静态库,而在动态链接库中还可以再包含其他的动态或静态链接库。
3)动态库就是在需要调用其中的函数时,根据函数映射表找到该函数然后调入堆栈执行。如果在当前工程中有多处对dll文件中同一个函数的调用,那么执行时,这个函数只会留下一份拷贝。但是如果有多处对lib文件中同一个函数的调用,那么执行时,该函数将在当前程序的执行空间里留下多份拷贝,而且是一处调用就产生一份拷贝。

## 181.在不使用额外空间的情况下,交换两个数?

1)算术
x = x + y;
y = x - y;
x = x - y;
2)异或
x = x^y;// 只能对int,char…
y = x^y;
x = x^y;
x ^= y ^= x;

## 182.strcpy和memcpy的区别

1、复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。
2、复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度。
3、用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy

## 183.执行int main(int argc, char *argv[])时的内存结构

参数的含义是程序在命令行下运行的时候,需要输入argc 个参数,每个参数是以char 类型输入的,依次存在数组里面,数组是 argv[],所有的参数在指针
char * 指向的内存中,数组的中元素的个数为 argc 个,第一个参数为程序的名称。

## 184.volatile关键字的作用?

volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。声明时语法:int volatile vInt; 当要求使用 volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。
volatile用在如下的几个地方:

中断服务程序中修改的供其它程序检测的变量需要加volatile;
多任务环境下各任务间共享的标志应该加volatile;
存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;

## 185.讲讲大端小端,如何检测(三种方法)

大端模式:是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址端。
小端模式,是指数据的高字节保存在内存的高地址中,低位字节保存在在内存的低地址端。
1)直接读取存放在内存中的十六进制数值,取低位进行值判断
int a = 0x12345678;
int *c = &a;
c[0] == 0x12 大端模式
c[0] == 0x78 小段模式
2)用共同体来进行判断
union共同体所有数据成员是共享一段内存的,后写入的成员数据将覆盖之前的成员数据,成员数据都有相同的首地址。Union的大小为最大数据成员的大小。
union的成员数据共用内存,并且首地址都是低地址首字节。Int i= 1时:大端存储1放在最高位,小端存储1放在最低位。当读取char ch时,是最低地址首字节,大小端会显示不同的值。
union w w p;
{ p.i = 1;
int i; if(ch == 1)
char ch;
};

## 186.查看内存的方法

1.首先打开vs编译器,创建好项目,并且将代码写进去,这里就不贴代码了,你可以随便的写个做个测试;
2.调试的时候做好相应的断点,然后点击开始调试;
3.程序调试之后会在你设置断点的地方暂停,然后选择调试->窗口->内存,就打开了内存数据查看的窗口了。

## 187.空类会默认添加哪些东西?怎么写?

1)Empty(); // 缺省构造函数//
2)Empty( const Empty& ); // 拷贝构造函数//
3)~Empty(); // 析构函数//
4)Empty& operator=( const Empty& ); // 赋值运算符//

## 188.标准库是什么?

1)C++ 标准库可以分为两部分:
标准函数库: 这个库是由通用的、独立的、不属于任何类的函数组成的。函数库继承自 C 语言。
面向对象类库: 这个库是类及其相关函数的集合。
2)输入/输出 I/O、字符串和字符处理、数学、时间、日期和本地化、动态分配、其他、宽字符函数
3)标准的 C++ I/O 类、String 类、数值类、STL 容器类、STL 算法、STL 函数对象、STL 迭代器、STL 分配器、本地化库、异常处理类、杂项支持库
118.const char* 与string之间的关系,传递参数问题?
1)string 是c++标准库里面其中一个,封装了对字符串的操作,实际操作过程我们可以用const char*给string类初始化
2)三者的转化关系如下所示:

```
a)string转const char* 
string s = “abc”; 
const char* c_s = s.c_str(); 
b)const char* 转string,直接赋值即可 
const char* c_s = “abc”; 
string s(c_s); 
c)string 转char* 
string s = “abc”; 
char* c; 
const int len = s.length(); 
c = new char[len+1]; 
strcpy(c,s.c_str()); 
d)char* 转string 
char* c = “abc”; 
string s(c); 
e)const char* 转char* 
const char* cpc = “abc”; 
char* pc = new char[strlen(cpc)+1]; 
strcpy(pc,cpc);
f)char* 转const char*,直接赋值即可 
char* pc = “abc”; 
const char* cpc = pc;
```



## 189.new、delete、operator new、operator delete、placement new、placement delete

1)new operator
new operator 完成了两件事情:用于申请内存和初始化对象。
例如:string* ps = new string(“abc”);
2)operator new
operator new 类似于C语言中的malloc,只是负责申请内存。
例如:void* buffer = operator new(sizeof(string)); 注意这里new前要有个operator。
3)placement new
用于在给定的内存中初始化对象。
例如:void* buffer = operator new(sizeof(string));buffer = new(buffer) string(“abc”); 调用了placement new,在buffer所指向的内存中创建了一个string类型的对象并且初始值为“abc”。
4)因此可以看出:new operator 可以分解operator new 和 placement new两个动作,是 operator new 和 placement new 的结合。与new对应的delete没有 placement delete 语法,它只有两种,分别是delete operator 和 operator delete。delete operator 和 new operator 对应,完成析构对象和释放内存的操作。而 operator delete 只是用于内存的释放,与C语言中的free相似。

## 190.为什么拷贝构造函数必须传引用不能传值?

拷贝构造函数的作用就是用来复制对象的,在使用这个对象的实例来初始化这个对象的一个新的实例。
参数传递过程到底发生了什么?
将地址传递和值传递统一起来,归根结底还是传递的是"值"(地址也是值,只不过通过它可以找到另一个值)!
i)值传递:
对于内置数据类型的传递时,直接赋值拷贝给形参(注意形参是函数内局部变量);
对于类类型的传递时,需要首先调用该类的拷贝构造函数来初始化形参(局部对象);如void foo(class_type obj_local){}, 如果调用foo(obj); 首先class_type obj_local(obj) ,这样就定义了局部变量obj_local供函数内部使用
ii)引用传递:
无论对内置类型还是类类型,传递引用或指针最终都是传递的地址值!而地址总是指针类型(属于简单类型), 显然参数传递时,按简单类型的赋值拷贝,而不会有拷贝构造函数的调用(对于类类型).
上述1) 2)回答了为什么拷贝构造函数使用值传递会产生无限递归调用,内存溢出。
拷贝构造函数用来初始化一个非引用类类型对象,如果用传值的方式进行传参数,那么构造实参需要调用拷贝构造函数,而拷贝构造函数需要传递实参,所以会一直递归。

## 191.空类的大小是多少?为什么?

1)C++空类的大小不为0,不同编译器设置不一样,vs设置为1;
2)C++标准指出,不允许一个对象(当然包括类对象)的大小为0,不同的对象不能具有相同的地址;
3)带有虚函数的C++类大小不为1,因为每一个对象会有一个vptr指向虚函数表,具体大小根据指针大小确定;
4)C++中要求对于类的每个实例都必须有独一无二的地址,那么编译器自动为空类分配一个字节大小,这样便保证了每个实例均有独一无二的内存地址。

## 192.你什么情况用指针当参数,什么时候用引用,为什么?

1)使用引用参数的主要原因有两个:
程序员能修改调用函数中的数据对象
通过传递引用而不是整个数据–对象,可以提高程序的运行速度
2)一般的原则:
对于使用引用的值而不做修改的函数:
如果数据对象很小,如内置数据类型或者小型结构,则按照值传递;
如果数据对象是数组,则使用指针(唯一的选择),并且指针声明为指向const的指针;
如果数据对象是较大的结构,则使用const指针或者引用,已提高程序的效率。这样可以节省结构所需的时间和空间;
如果数据对象是类对象,则使用const引用(传递类对象参数的标准方式是按照引用传递);
3)对于修改函数中数据的函数:
如果数据是内置数据类型,则使用指针
如果数据对象是数组,则只能使用指针
如果数据对象是结构,则使用引用或者指针
如果数据是类对象,则使用引用

## 193.大内存申请时候选用哪种?C++变量存在哪?变量的大小存在哪?符号表存在哪?

1.大内存申请时,采用堆申请空间,用new申请;
2.不同的变量存储在不同的地方,局部变量、全局变量、静态变量;
3.C++对变量名不作存储,在汇编以后不会出现变量名,变量名作用只是用于方便编译成汇编代码,是给编译器看的,是方便人阅读的

## 194.为什么会有大端小端,htol这一类函数的作用

1)这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为 8bit。但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(要看具体的编译器),另外,对于位数大于 8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。例如一个16bit的short型x,在内存中的地址为0x0010,x的值为0x1122,那么0x11为高字节,0x22为低字节。对于 大端模式,就将0x11放在低地址中,即0x0010中,0x22放在高地址中,即0x0011中。小端模式,刚好相反。我们常用的X86结构是小端模式,而KEIL C51则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。

## 195.静态函数能定义为虚函数吗?常函数?

1、static成员不属于任何类对象或类实例,所以即使给此函数加上virutal也是没有任何意义的。2. 静态与非静态成员函数之间有一个主要的区别。那就是静态成员函数没有this指针。虚函数依靠vptr和vtable来处理。vptr是一个指针,在类的构造函数中创建生成,并且只能用this指针来访问它,因为它是类的一个成员,并且vptr指向保存虚函数地址的vtable.对于静态成员函数,它没有this指针,所以无法访问vptr. 这就是为何static函数不能为virtual.虚函数的调用关系:this -> vptr -> vtable ->virtual function

## 196.this指针调用成员变量时,堆栈会发生什么变化?

当在类的非静态成员函数访问类的非静态成员时,编译器会自动将对象的地址传给作为隐含参数传递给函数,这个隐含参数就是this指针。即使你并没有写this指针,编译器在链接时也会加上this的,对各成员的访问都是通过this的。例如你建立了类的多个对象时,在调用类的成员函数时,你并不知道具体是哪个对象在调用,此时你可以通过查看this指针来查看具体是哪个对象在调用。This指针首先入栈,然后成员函数的参数从右向左进行入栈,最后函数返回地址入栈。

## 197.静态绑定和动态绑定的介绍

1)对象的静态类型:对象在声明时采用的类型。是在编译期确定的。
2)对象的动态类型:目前所指对象的类型。是在运行期决定的。对象的动态类型可以更改,但是静态类型无法更改。
3)静态绑定:绑定的是对象的静态类型,某特性(比如函数)依赖于对象的静态类型,发生在编译期。
4)动态绑定:绑定的是对象的动态类型,某特性(比如函数)依赖于对象的动态类型,发生在运行期。

## 198.设计一个类计算子类的个数

1.为类设计一个static静态变量count作为计数器;
2.类定义结束后初始化count;
3.在构造函数中对count进行+1;
4.设计拷贝构造函数,在进行拷贝构造函数中进行count +1,操作;
5.设计复制构造函数,在进行复制函数中对count+1操作;
6.在析构函数中对count进行-1;

## 199.怎么快速定位错误出现的地方

1.如果是简单的错误,可以直接双击错误列表里的错误项或者生成输出的错误信息中带行号的地方就可以让编辑窗口定位到错误的位置上。
2.对于复杂的模板错误,最好使用生成输出窗口。多数情况下出发错误的位置是最靠后的引用位置。如果这样确定不了错误,就需要先把自己写的代码里的引用位置找出来,然后逐个分析了。

## 200.虚函数的代价?

1)带有虚函数的类,每一个类会产生一个虚函数表,用来存储指向虚成员函数的指针,增大类;
2)带有虚函数的类的每一个对象,都会有有一个指向虚表的指针,会增加对象的空间大小;
3)不能再是内敛的函数,因为内敛函数在编译阶段进行替代,而虚函数表示等待,在运行阶段才能确定到低是采用哪种函数,虚函数不能是内敛函数。

## 201.类对象的大小

1)类的非静态成员变量大小,静态成员不占据类的空间,成员函数也不占据类的空间大小;
2)内存对齐另外分配的空间大小,类内的数据也是需要进行内存对齐操作的;
3)虚函数的话,会在类对象插入vptr指针,加上指针大小;
4)当该该类是某类的派生类,那么派生类继承的基类部分的数据成员也会存在在派生类中的空间中,也会对派生类进行扩展。

## 202.移动构造函数

1)有时候我们会遇到这样一种情况,我们用对象a初始化对象b后对象a我们就不在使用了,但是对象a的空间还在呀(在析构之前),既然拷贝构造函数,实际上就是把a对象的内容复制一份到b中,那么为什么我们不能直接使用a的空间呢?这样就避免了新的空间的分配,大大降低了构造的成本。这就是移动构造函数设计的初衷;
2)拷贝构造函数中,对于指针,我们一定要采用深层复制,而移动构造函数中,对于指针,我们采用浅层复制;
3)C++引入了移动构造函数,专门处理这种,用a初始化b后,就将a析构的情况;
4)与拷贝类似,移动也使用一个对象的值设置另一个对象的值。但是,又与拷贝不同的是,移动实现的是对象值真实的转移(源对象到目的对象):源对象将丢失其内容,其内容将被目的对象占有。移动操作的发生的时候,是当移动值的对象是未命名的对象的时候。这里未命名的对象就是那些临时变量,甚至都不会有名称。典型的未命名对象就是函数的返回值或者类型转换的对象。使用临时对象的值初始化另一个对象值,不会要求对对象的复制:因为临时对象不会有其它使用,因而,它的值可以被移动到目的对象。做到这些,就要使用移动构造函数和移动赋值:当使用一个临时变量对象进行构造初始化的时候,调用移动构造函数。类似的,使用未命名的变量的值赋给一个对象时,调用移动赋值操作;
5)

```
Example6 (Example6&& x) : ptr(x.ptr) 
    {
        x.ptr = nullptr;
    }
    // move assignment
    Example6& operator= (Example6&& x) 
    {
        delete ptr; 
        ptr = x.ptr;
        x.ptr=nullptr;
        return *this;
}
```



## 203.何时需要合成构造函数

1)如果一个类没有任何构造函数,但他含有一个成员对象,该成员对象含有默认构造函数,那么编译器就为该类合成一个默认构造函数,因为不合成一个默认构造函数那么该成员对象的构造函数不能调用;
2)没有任何构造函数的类派生自一个带有默认构造函数的基类,那么需要为该派生类合成一个构造函数,只有这样基类的构造函数才能被调用;
3)带有虚函数的类,虚函数的引入需要进入虚表,指向虚表的指针,该指针是在构造函数中初始化的,所以没有构造函数的话该指针无法被初始化;
4)带有一个虚基类的类

1)并不是任何没有构造函数的类都会合成一个构造函数
2)编译器合成出来的构造函数并不会显示设定类内的每一个成员变量

## 204.何时需要合成复制构造函数

有三种情况会以一个对象的内容作为另一个对象的初值:
1)对一个对象做显示的初始化操作,X xx = x;
2)当对象被当做参数交给某个函数时;
3)当函数传回一个类对象时;

1)如果一个类没有拷贝构造函数,但是含有一个类类型的成员变量,该类型含有拷贝构造函数,此时编译器会为该类合成一个拷贝构造函数;
2)如果一个类没有拷贝构造函数,但是该类继承自含有拷贝构造函数的基类,此时编译器会为该类合成一个拷贝构造函数;
3)如果一个类没有拷贝构造函数,但是该类声明或继承了虚函数,此时编译器会为该类合成一个拷贝构造函数;
4)如果一个类没有拷贝构造函数,但是该类含有虚基类,此时编译器会为该类合成一个拷贝构造函数;

## 205.何时需要成员初始化列表?过程是什么?

1)当初始化一个引用成员变量时;
2)初始化一个const成员变量时;
3)当调用一个基类的构造函数,而构造函数拥有一组参数时;
4)当调用一个成员类的构造函数,而他拥有一组参数;
5)编译器会一一操作初始化列表,以适当顺序在构造函数之内安插初始化操作,并且在任何显示用户代码前。list中的项目顺序是由类中的成员声明顺序决定的,不是初始化列表中的排列顺序决定的。

## 206.程序员定义的析构函数被扩展的过程?

1)析构函数函数体被执行;
2)如果class拥有成员类对象,而后者拥有析构函数,那么它们会以其声明顺序的相反顺序被调用;
3)如果对象有一个vptr,现在被重新定义
4)如果有任何直接的上一层非虚基类拥有析构函数,则它们会以声明顺序被调用;
5)如果任何虚基类拥有析构函数

## 207.构造函数的执行算法?

1)在派生类构造函数中,所有的虚基类及上一层基类的构造函数调用;
2)对象的vptr被初始化;
3)如果有成员初始化列表,将在构造函数体内扩展开来,这必须在vptr被设定之后才做;
4)执行程序员所提供的代码;

## 208.构造函数的扩展过程?

1)记录在成员初始化列表中的数据成员初始化操作会被放在构造函数的函数体内,并与成员的声明顺序为顺序;
2)如果一个成员并没有出现在成员初始化列表中,但它有一个默认构造函数,那么默认构造函数必须被调用;
3)如果class有虚表,那么它必须被设定初值;
4)所有上一层的基类构造函数必须被调用;
5)所有虚基类的构造函数必须被调用。

## 209.哪些函数不能是虚函数

1)构造函数,构造函数初始化对象,派生类必须知道基类函数干了什么,才能进行构造;当有虚函数时,每一个类有一个虚表,每一个对象有一个虚表指针,虚表指针在构造函数中初始化;
2)内联函数,内联函数表示在编译阶段进行函数体的替换操作,而虚函数意味着在运行期间进行类型确定,所以内联函数不能是虚函数;
3)静态函数,静态函数不属于对象属于类,静态成员函数没有this指针,因此静态函数设置为虚函数没有任何意义。
4)友元函数,友元函数不属于类的成员函数,不能被继承。对于没有继承特性的函数没有虚函数的说法。
5)普通函数,普通函数不属于类的成员函数,不具有继承特性,因此普通函数没有虚函数。

## 210.sizeof 和strlen 的区别

1)strlen计算字符串的具体长度(只能是字符串),不包括字符串结束符。返回的是字符个数。
2)sizeof计算声明后所占的内存数(字节大小),不是实际长度。
3)sizeof是一个取字节运算符,而strlen是个函数。
4)sizeof的返回值=字符个数字符所占的字节数,字符实际长度小于定义的长度,此时字符个数就等于定义的长度。若未给出定义的大小,分类讨论,对于字符串数组,字符大 小等于实际的字符个数+1;对于整型数组,字符个数为实际的字符个数。字符串每个字符占1个字节,整型数据每个字符占的字节数需根据系统的位数类确定,32位占4个字节。
5)sizeof可以用类型做参数,strlen只能用char做参数,且必须以‘\0’结尾,sizeof还可以用函数做参数;
6)数组做sizeof的参数不退化,传递给strlen就退化为指针;

## 211.简述strcpy、sprintf与memcpy的区别

1)操作对象不同
①strcpy的两个操作对象均为字符串
②sprintf的操作源对象可以是多种数据类型,目的操作对象是字符串
③memcpy的两个对象就是两个任意可操作的内存地址,并不限于何种数据类型。
2)执行效率不同
memcpy最高,strcpy次之,sprintf的效率最低。
3)实现功能不同
①strcpy主要实现字符串变量间的拷贝
②sprintf主要实现其他数据类型格式到字符串的转化
③memcpy主要是内存块间的拷贝。

## 212.编码实现某一变量某位清0或置1

```
#define BIT3 (0x1 << 3 ) Satic int a;
//设置a的bit 3:
void set_bit3( void )
{
a |= BIT3; //将a第3位置1
}
//清a的bit 3
void set_bit3( void )
{
a &= ~BIT3; //将a第3位清零
}
```

## 213.将“引用”作为函数参数有哪些特点?

1)传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。
2)使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。
3)使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。

## 214.分别写出BOOL,int,float,指针类型的变量a 与“零”的比较语句。

```
BOOL : if ( !a ) or if(a)
int : if ( a == 0)
float : const EXPRESSION EXP = 0.000001
if ( a < EXP && a >-EXP)
pointer : if ( a != NULL) or if(a == NULL)
```

无论是float还是double类型的变量,都有精度限制。所以一定要避免将浮点变量用“==”或“!=”与数字比较,应该设法转化成“>=”或“<=”形式。

## 215.局部变量全局变量的问题?

1)局部会屏蔽全局。要用全局变量,需要使用"::"局部变量可以与全局变量同名,在函数内引用这个变量时,会用到同名的局部变量,而不会用到全局变量。对于有些编译器而言,在同一个函数内可以定义多个同名的局部变量,比如在两个循环体内都定义一个同名的局部变量,而那个局部变量的作用域就在那个循环体内。
2)如何引用一个已经定义过的全局变量,可以用引用头文件的方式,也可以用extern关键字,如果用引用头文件方式来引用某个在头文件中声明的全局变理,假定你将那个变写错了,那么在编译期间会报错,如果你用extern方式引用时,假定你犯了同样的错误,那么在编译期间不会报错,而在连接期间报错。
3)全局变量可不可以定义在可被多个.C文件包含的头文件中,在不同的C文件中以static形式来声明同名全局变量。可以在不同的C文件中声明同名的全局变量,前提是其中只能有一个C文件中对此变量赋初值,此时连接不会出错

## 216.数组和指针的区别?

1)数组在内存中是连续存放的,开辟一块连续的内存空间;数组所占存储空间:sizeof(数组名);数组大小:sizeof(数组名)/sizeof(数组元素数据类型);
2)用运算符sizeof 可以计算出数组的容量(字节数)。sizeof§,p 为指针得到的是一个指针变量的字节数,而不是p 所指的内存容量。
3)编译器为了简化对数组的支持,实际上是利用指针实现了对数组的支持。具体来说,就是将表达式中的数组元素引用转换为指针加偏移量的引用。
4)在向函数传递参数的时候,如果实参是一个数组,那用于接受的形参为对应的指针。也就是传递过去是数组的首地址而不是整个数组,能够提高效率;
5)在使用下标的时候,两者的用法相同,都是原地址加上下标值,不过数组的原地址就是数组首元素的地址是固定的,指针的原地址就不是固定的。

## 217.C++如何阻止一个类被实例化?一般在什么时候将构造函数声明为private?

1)将类定义为抽象基类或者将构造函数声明为private;
2)不允许类外部创建类对象,只能在类内部创建对象

## 218.如何禁止自动生成拷贝构造函数?

1)为了阻止编译器默认生成拷贝构造函数和拷贝赋值函数,我们需要手动去重写这两个函数,某些情况下,为了避免调用拷贝构造函数和拷贝赋值函数,我们需要将他们设置成private,防止被调用。
2)类的成员函数和friend函数还是可以调用private函数,如果这个private函数只声明不定义,则会产生一个连接错误;
3)针对上述两种情况,我们可以定一个base类,在base类中将拷贝构造函数和拷贝赋值函数设置成private,那么派生类中编译器将不会自动生成这两个函数,且由于base类中该函数是私有的,因此,派生类将阻止编译器执行相关的操作。

## 219.assert与NDEBUGE

1)assert宏的原型定义在<assert.h>中,其作用是如果它的条件返回错误,则终止程序执行,原型定义:
#include <assert.h>
void assert( int expression );
assert的作用是现计算表达式 expression ,如果其值为假(即为0),那么它先向stderr打印一条出错信息,然后通过调用 abort 来终止程序运行。如果表达式为真,assert什么也不做。
2)NDEBUG宏是Standard C中定义的宏,专门用来控制assert()的行为。如果定义了这个宏,则assert不会起作用。定义NDEBUG能避免检查各种条件所需的运行时开销,当然此时根本就不会执行运行时检查。
3)C Standard中规定了assert以宏来实现。<assert.h>被设计来可以被多次包含,其中一上来就undef assert,然后由NDEBUG宏来决定其行为。

## 220.Denug和release的区别

1)调试版本,包含调试信息,所以容量比Release大很多,并且不进行任何优化(优化会使调试复杂化,因为源代码和生成的指令间关系会更复杂),便于程序员调试。Debug模式下生成两个文件,除了.exe或.dll文件外,还有一个.pdb文件,该文件记录了代码中断点等调试信息;
2)发布版本,不对源代码进行调试,编译时对应用程序的速度进行优化,使得程序在代码大小和运行速度上都是最优的。(调试信息可在单独的PDB文件中生成)。Release模式下生成一个文件.exe或.dll文件。
3)实际上,Debug 和 Release 并没有本质的界限,他们只是一组编译选项的集合,编译器只是按照预定的选项行动。事实上,我们甚至可以修改这些选项,从而得到优化过的调试版本或是带跟踪语句的发布版本。

## 221.main函数有没有返回值

1)程序运行过程入口点main函数,main()函数返回值类型必须是int,这样返回值才能传递给程序激活者(如操作系统)表示程序正常退出。main(int args, char **argv) 参数的传递。参数的处理,一般会调用getopt()函数处理,但实践中,这仅仅是一部分,不会经常用到的技能点。

## 222.写一个比较大小的模板函数

```
#include<iostream>  
using namespace std;  
template<typename type1,typename type2>//函数模板  
type1 Max(type1 a,type2 b)  
{  

return a > b ? a : b;  
}  
void main()  
{  

cout<<"Max = "<<Max(5.5,'a')<<endl;  
}  
```

## 223.c++怎么实现一个函数先于main函数运行

1)如果在main函数之前声明一个类的全局的对象。那么其执行顺序,根据全局对象的生存期和作用域,肯定先于main函数。

```
class simpleClass  
{  
public:  
       simpleClass( )  
       {  
              cout << "simpleClass constructor.." << endl;         }  
};     
simpleClass g_objectSimple;         //step1全局对象  
int _tmain(int argc, _TCHAR* argv[])  //step3  
{  
       return 0;  
}  
```


2)定义在main( )函数之前的全局对象、静态对象的构造函数在main( )函数之前执行。
3)Main函数执行之前,主要就是初始化系统相关资源;
①设置栈指针
②初始化static静态和global全局变量,即data段的内容
③将未初始化部分的全局变量赋初值:数值型short,int,long等为0,bool为FALSE,指针为NULL,等等,即.bss段的内容
④全局对象初始化,在main之前调用构造函数
⑤将main函数的参数,argc,argv等传递给main函数,然后才真正运行main函数
4)Main函数执行之后
①全局对象的析构函数会在main函数之后执行;
②可以用_onexit 注册一个函数,它会在main 之后执行;

## 224.虚函数与纯虚函数的区别在于

1)纯虚函数只有定义没有实现,虚函数既有定义又有实现;
2)含有纯虚函数的类不能定义对象,含有虚函数的类能定义对象;

## 225.智能指针怎么用?智能指针出现循环引用怎么解决?

1)shared_ptr
调用一个名为make_shared的标准库函数,shared_ptr p = make_shared(42);通常用auto更方便,auto p = …;shared_ptr p2(new int(2));
每个shared_ptr都有一个关联的计数器,通常称为引用计数,一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象;shared_ptr的析构函数就会递减它所指的对象的引用计数。如果引用计数变为0,shared_ptr的析构函数就会销毁对象,并释放它占用的内存。
2)unique_ptr
一个unique_ptr拥有它所指向的对象。某个时刻只能有一个unique_ptr指向一个给定对象。当unique_ptr被销毁时,它所指向的对象也被销毁。
3)weak_ptr
weak_ptr是一种不控制所指向对象生存期的智能指针,它指向由一个shared_ptr管理的对象,将一个weak_ptr绑定到一个shared_ptr不会改变引用计数,一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放,即使有weak_ptr指向对象,对象还是会被释放。
4)弱指针用于专门解决shared_ptr循环引用的问题,weak_ptr不会修改引用计数,即其存在与否并不影响对象的引用计数器。循环引用就是:两个对象互相使用一个shared_ptr成员变量指向对方。弱引用并不对对象的内存进行管理,在功能上类似于普通指针,然而一个比较大的区别是,弱引用能检测到所管理的对象是否已经被释放,从而避免访问非法内存。

## 226.strcpy函数和strncpy函数的区别?哪个函数更安全?

1)函数原型
char* strcpy(char* strDest, const char* strSrc)
char* strncpy(char* strDest, const char* strSrc, int pos)
2)strcpy函数: 如果参数 dest 所指的内存空间不够大,可能会造成缓冲溢出(buffer Overflow)的错误情况,在编写程序时请特别留意,或者用strncpy()来取代。
strncpy函数:用来复制源字符串的前n个字符,src 和 dest 所指的内存区域不能重叠,且 dest 必须有足够的空间放置n个字符。
3)如果目标长>指定长>源长,则将源长全部拷贝到目标长,自动加上’\0’
如果指定长<源长,则将源长中按指定长度拷贝到目标字符串,不包括’\0’
如果指定长>目标长,运行时错误 ;

## 227.为什么要用static_cast转换而不用c语言中的转换?

1)更加安全;
2)更直接明显,能够一眼看出是什么类型转换为什么类型,容易找出程序中的错误;可清楚地辨别代码中每个显式的强制转;可读性更好,能体现程序员的意图

## 228.成员函数里memset(this,0,sizeof(*this))会发生什么

1)有时候类里面定义了很多int,char,struct等c语言里的那些类型的变量,我习惯在构造函数中将它们初始化为0,但是一句句的写太麻烦,所以直接就memset(this, 0, sizeof *this);将整个对象的内存全部置为0。对于这种情形可以很好的工作,但是下面几种情形是不可以这么使用的;
2)类含有虚函数表:这么做会破坏虚函数表,后续对虚函数的调用都将出现异常;
3)类中含有C++类型的对象:例如,类中定义了一个list的对象,由于在构造函数体的代码执行之前就对list对象完成了初始化,假设list在它的构造函数里分配了内存,那么我们这么一做就破坏了list对象的内存。

## 229.方法调用的原理(栈,汇编)

1)机器用栈来传递过程参数、存储返回信息、保存寄存器用于以后恢复,以及本地存储。而为单个过程分配的那部分栈称为帧栈;帧栈可以认为是程序栈的一段,它有两个端点,一个标识起始地址,一个标识着结束地址,两个指针结束地址指针esp,开始地址指针ebp;
2)由一系列栈帧构成,这些栈帧对应一个过程,而且每一个栈指针+4的位置存储函数返回地址;每一个栈帧都建立在调用者的下方,当被调用者执行完毕时,这一段栈帧会被释放。由于栈帧是向地址递减的方向延伸,因此如果我们将栈指针减去一定的值,就相当于给栈帧分配了一定空间的内存。如果将栈指针加上一定的值,也就是向上移动,那么就相当于压缩了栈帧的长度,也就是说内存被释放了。
3)过程实现
①备份原来的帧指针,调整当前的栈帧指针到栈指针位置;
②建立起来的栈帧就是为被调用者准备的,当被调用者使用栈帧时,需要给临时变量分配预留内存;
③使用建立好的栈帧,比如读取和写入,一般使用mov,push以及pop指令等等。
④恢复被调用者寄存器当中的值,这一过程其实是从栈帧中将备份的值再恢复到寄存器,不过此时这些值可能已经不在栈顶了
⑤恢复被调用者寄存器当中的值,这一过程其实是从栈帧中将备份的值再恢复到寄存器,不过此时这些值可能已经不在栈顶了。
⑥释放被调用者的栈帧,释放就意味着将栈指针加大,而具体的做法一般是直接将栈指针指向帧指针,因此会采用类似下面的汇编代码处理。
⑦恢复调用者的栈帧,恢复其实就是调整栈帧两端,使得当前栈帧的区域又回到了原始的位置。
⑧弹出返回地址,跳出当前过程,继续执行调用者的代码。
4)过程调用和返回指令
①call指令
②leave指令
③ret指令

## 230.MFC消息处理如何封装的?

## 231.回调函数的作用

1)当发生某种事件时,系统或其他函数将会自动调用你定义的一段函数;
2)回调函数就相当于一个中断处理函数,由系统在符合你设定的条件时自动调用。为此,你需要做三件事:1,声明;2,定义;3,设置触发条件,就是在你的函数中把你的回调函数名称转化为地址作为一个参数,以便于系统调用;
3)回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数;
4)因为可以把调用者与被调用者分开。调用者不关心谁是被调用者,所有它需知道的,只是存在一个具有某种特定原型、某些限制条件(如返回值为int)的被调用函数。

## 232.随机数的生成

1)#include<time.h> srand((unsigned)time(NULL)); cout<<(rand()%(b-a))+a;
2)由于rand()的内部实现是用线性同余法做的,所以生成的并不是真正的随机数,而是在一定范围内可看为随机的伪随机数。
3)种子写为srand(time(0))代表着获取系统时间,电脑右下角的时间,每一秒后系统时间的改变,数字序列的改变得到的数字不同,这才得带不同的数字,形成了真随机数,即使是真随机数,也是有规律可循。
————————————————
版权声明:本文为CSDN博主「绝代风华i」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_42581177/article/details/125831432

## 233.变量的声明和定义有什么区别?

变量的定义为变量分配地址和存储空间,变量的声明不会分配地址,一个变量可以在多个地方声明,但是只能在一个地方定义,加入extern关键字修饰的是变量的声明,说明此变量将在文件以外或者文件后面部分定义。

说明:很多时候一个变量,只是声明不分配内存空间,直到具体使用时才初始化,分配内存空间,如外部变量。

[![复制代码](https://common.cnblogs.com/images/copycode.gif)](javascript:void(0);)

```
int main()
{
  extern int A;
  //这是个声明而不是定义,声明A是一个已经定义了的外部变量

  dosth();//执行函数
}

int A;  //定义A变量
```

[![复制代码](https://common.cnblogs.com/images/copycode.gif)](javascript:void(0);)

 

## 234.请简述#ifdef、#else、#endif、和#ifndef的作用是?

- 利用#ifdef、#endif将某程序功能模块包括进去,以向特定用户提供该功能,在不需要时,用户可以轻易将其屏蔽;

```
#ifdef MATH
#include "math.c"
#endif
```

- 在子程序上加上标记,以便于追踪和调试;

```
#ifdef DEBUG
printf("Indebugging.......!\r\n");
#endif
```

- 应对硬件的限制,由于一些具体应用环境的硬件不一样,限于条件,本地缺乏某种设备,只能绕过硬件,直接写出预期结果。

注意:虽然不用条件编译命令而直接用if语句也能到达要求,但是这样做的话,目标程序长(所有的语句都需要编译),运行时间长(在程序运行时会对if语句进行测试),采用条件编译,可以减少被编译的语句,减少目标程序的长度,减少程序的运行时间。

 

## 235.请写出int、bool、float、指针变量与"零值"比较的if语句?

[![复制代码](https://common.cnblogs.com/images/copycode.gif)](javascript:void(0);)

```
//int类型与零值比较
if (n == 0)
if (n != 0)

//bool类型与零值比较
if (flag)    //表示flag为真
if (!flag)    //表示flag为假

//float类型与零值比较
const float EPSINON = 0.00001;
if ((x >= -EPSINON) && (x <= EPSINON))    //其中EPSINON是允许的误差

//指针变量与零值比较
if (p == NULL)
if (p != NULL)
```

[![复制代码](https://common.cnblogs.com/images/copycode.gif)](javascript:void(0);)

 

## 236.结构体是否可以直接赋值?

声明时可以直接初始化,同一结构体的不同对象之间也可以直接赋值,但是当结构体中含有指针成员时一定要小心。

注意:当有多个指针指向同一段内存时,某个指针释放这段内存可能会导致其它指针的非法操作,因此在释放前一定要确保其它指针不再使用这段内存空间。

 

## 237.sizeof和strlen的区别?

- sizeof是一个操作符,strlen是库函数;
- sizeof的参数可以是数据的类型,也可以是变量,而strlen只能以结尾为'\0'的字符串作为参数;
- 编译器在编译时就计算出了sizeof的结果,而strlen函数必须在程序运行时才能计算出来,并且sizeof计算的是数据类型占内存的大小,而strlen计算的是字符串实际占用的内存大小;
- 数组做sizeof的参数不退化,数组传递给strlen就退化为指针了。

 

## 238.C语言和C++语言中的关键字static有什么区别?

在C语言中static用来修饰局部静态变量和外部静态变量、函数,而C++中的static关键字除了具有上述功能外,还能用来定义类的成员变量和函数,也就是静态成员和静态成员函数。

注意:编程时static的记忆性和全局性特点可以让在不同时期调用的函数进行通信、传递信息,而C++的静态成员则可以在多个对象实例间进行通信、传递信息。

 

## 239.C语言的malloc和C++中的new有什么区别?

- new和delete是操作符,可以重载,只能在C++中使用;
- malloc和free是函数,可以覆盖,C和C++都能使用;
- new可以调用对象的构造函数,对应的delete调用对象的析构函数;
- malloc仅仅分配内存,free则是回收内存,并不会执行构造函数和析构函数;
- new、delete返回的是某种数据类型指针,malloc、free返回的是void指针。

注意:malloc申请的内存空间要使用free进行释放,而new申请的内存空间要使用delete释放,不能够混用。

 

## 240.请写一个标准宏MIN?

```
#define min(a, b)    ((a) <= (b) ? (a) : (b))
```

 

## 241.++i和i++的区别?

++i先自增1,再返回,i++先返回i,再自增1。

 

## 242.关键字volatile有什么作用?

- 状态寄存器一类的并行设备硬件寄存器;
- 中断服务子程序会访问到的非自动变量;
- 多线程间被几个任务访问共享变量。

注意:虽然volatile在嵌入式方面应用比较多,但是在PC软件的多线程中,volatile修饰的临界变量也是非常实用的。

 

## 243.一个参数可以既是const又是volatile吗?

一个参数既可以是const又可以是volatile的,当使用const和volatile同时修饰变量时,表示这个变量在程序内部是只读的,不能改变的,只在程序外部条件变化下改变,并且编译器不会优化这个变量,每次使用这个变量时,都会小心地去内存读取这个变量的值,而不是去寄存器读取它的备份。

注意:在这一定要注意const的意思,const只是不允许程序中的代码改变某一变量,其在程序编译器发挥作用,它并没有实际地禁止某段内存的读写特性。

 

## 244.*a和&a有什么区别?

&a:含义就是取变量a的地址

*a:使用在不同的地方,含义也不一样

- 在声明语句中,*a只说明a是一个指针变量,例如int *a;
- 在其它语句中,*a前面没有操作数并且a是一个指针时,*a代表指针a指向的内存地址存放的数据,如b=*a;
- *a前面有操作数并且a是一个普通变量时,*a代表乘以a,如c=b*a。

 

## 245.用C语言编写一个死循环程序?

[![复制代码](https://common.cnblogs.com/images/copycode.gif)](javascript:void(0);)

```
while (1)
{
  dosth();      
}

for (;;)
{
  dosth();  
}
```

[![复制代码](https://common.cnblogs.com/images/copycode.gif)](javascript:void(0);)

注意:很多种途径都可以实现同一种功能,但是不同的方式,时间和空间占用度不同,特别是对于嵌入式软件,处理器速度比较慢,存储空间有限,所以时间和空间优势是选择各种方法的首要考虑条件。

 

## 246.结构体内存对齐问题?

https://www.cnblogs.com/Cqlismy/p/11440057.html

 

## 247.全局变量和局部变量有什么区别?是怎么实现的?操作系统和编译器是怎么知道的?

- 全局变量是整个程序都可以访问的变量,生存期是从整个程序运行道结束,在程序结束时,全局变量所占用的内存会被释放;
- 局部变量存在于模块(子程序、函数)中,只有所在的模块可以访问,其它模块不能访问,模块结束(函数调用完成),局部变量所占用的内存被释放;
- 操作系统和编译器,可能是通过内存分配的位置来知道的,全局变量会被分配在全局数据段,并且在程序开始运行的时候被加载,局部变量则是在堆栈中进行分配的。

 

## 248.请简述C/C++程序编译的内存分配情况?

- 从静态存储区域分配:内存在程序编译时就已经分配好,这块内存在程序的整个运行期间都存在,速度快,不容易出错,例如:全局变量、static变量,常量字符串等;
- 从栈上分配:在执行函数时,函数内部局部变量的存储单元都在栈上创建,函数执行结束时,这些存储单元自动释放,栈内存分配运算内置于处理器的指令中,效率很高,但是分配的内存容量有限;
- 从堆上分配:也就是动态内存分配,程序在运行时使用malloc或者new申请任意大小的内存,程序员自己负责何时使用free或者delete进行内存释放,动态内存的生存期是由程序员决定的,使用非常灵活,如果在堆上分配了内存空间,就有责任回收它,否则运行的程序会出现内存泄漏,另外频繁地分配和释放不同大小的堆空间将会产生堆内碎块。
- 一个C/C++程序编译时内存分为5大存储区,分别是栈区、堆区、全局区、文字常量区、程序代码区。

 

## 249.请简述strcpy、sprintf和memcpy的区别?

- 操作对象不同,strcpy的两个操作对象均为字符串,sprintf的操作源对象可以是多种数据类型,目的操作对象是字符串,memcpy的两个对象就是任意可操作的内存地址,并不限于何种数据类型;
- 执行效率不同,memcpy的效率最高,strcpy次之,sprintf的效率最低;
- 实现功能不同,strcpy主要实现字符串变量间的拷贝,sprintf主要实现其它数据类型格式化到字符串,memcpy主要用于内存块间的拷贝;

注意:strcpy、sprintf和memcpy都可以实现拷贝的功能,但是针对的对象不同,需要根据实际需求,来选择合适的函数区实现拷贝功能。

 

## 250.请解释(*(void (*)())0)()的含义?

- void (*0)():是一个返回值为void,参数为空的函数指针0;
- (void (*)())0:把0转变成一个返回值为void,参数为空的函数指针;
- *(void (*)())0:在上句的基础上加*表示整个是一个返回值为void,无参数,并且起始地址为0的函数的名字;
- ((void (*)())0)():上句的函数名所对应的函数的调用。

 

## 251.C语言的指针和引用和C++的有什么区别?

- 指针有自己的一块空间,而引用只是一个别名;
- 使用sizeof看一个指针的大小是4字节,而引用则是被引用对象的大小;
- 作为参数传递时,指针需要被解引用才可以对对象进行操作,而直接对引用的修改都会改变引用所指向的对象;
- 可以有const指针,但是没有const引用;
- 指针在使用中可以指向其它对象,但是引用只能是一个对象的引用,不能被改变;
- 指针可以有多级指针,而引用只有一级;
- 指针和引用使用++运算符的意义不一样;
- 如果返回动态内存分配的对象或者内存,必须使用指针,引用可能会引起内存泄漏。

 

## 252.typedef和define有什么区别?

- 用法不同:typedef用来定义一种数据类型的别名,增强程序的可读性,define主要用来定义常量以及书写复杂使用频繁的宏;
- 执行时间不同:typedef是编译过程的一部分,有类型检查的功能,define是宏定义,是预编译的部分,其发生在编译之前,只是简单的进行字符串替换,不会进行类型的检查;
- 作用域不同:typedef有作用域限定,define不受作用域限定,只要是在define声明后的引用,都是正确的;
- 对指针的操作不同:typedef和define定义指针时有很大区别。

注意:typedef定义是语句,句尾需要加上分号,而define不是语句,不能在句尾加上分号。

 

## 253.指针常量和常量指针有什么区别?

指针常量是指定义了一个指针,这个指针的值只能在定义时初始化,其它地方不能改变。常量指针是指定义了一个指针,这个指针指向一个只读的对象,不能通过常量指针来改变这个对象的值。指针常量强调的是指针的不可变性,而常量指针强调的是指针对其所指对象的不可变性。

注意:无论是指针常量还是常量指针,其最大的用途就是作为函数的形式参数,保证实参在被调用函数中的不可改变特性。

 

## 254.请简述队列和栈的异同?

队列和栈都是线性存储结构,但是两者的插入和删除数据的操作不同,队列是"先进先出",栈是“先进后出”。

注意:区别栈区和堆区,堆区的存取是"顺序随意",而栈区是"先进后出",栈是由编译器自动分配释放,存放函数的参数值,局部变量的值等,其操作方式类似于数据结构中的栈,堆一般是由程序员分配释放,若程序员不释放,程序结束时可能由OS回收,分配方式类似于链表,它与本题中的堆和栈是两回事,堆栈只是一种数据结构,而堆区和栈区是程序的不同内存存储区域。

 

## 255.如何设置地址为0x67a9的整型变量的值为0xaa66?

```
int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa66;
```

注意:这道题就是强制类型转换的典型例子,无论在什么平台,地址长度和整型数据的长度是一样的,既一个整型数据可以强制转换成地址指针类型,只要有意义即可。

 

## 256.请编程实现字符串转换为数字?

编码实现函数atoi(),设计一个程序,把一个字符串转化为一个整型数值,例如:字符串"5486321",转化成整型5486321。

[![复制代码](https://common.cnblogs.com/images/copycode.gif)](javascript:void(0);)

```
int myatoi(const char *str)
{
    int num = 0;  //保存转换后的数值
    int isNegative = 0;  //记录字符串中是否有负号
    int n = 0;
    char *p = str;

    if (p == NULL)  //判断指针的合法性
        return -1;
    
    while (*p++ != '\0')  //计算字符串长度
        n++;
    
    p = str;
    if (p[0] == '-')  //判断数组是否有负号
        isNegative = 1;

    for (int i = 0; i < n; i++)
    {
        char temp = *p++;
        if (temp > '9' || temp < '0')  //滤除非数字字符
            continue;
        
        if (num != 0 || temp != '0')  //滤除字符串开始的'0'字符
        {
            temp -= 0x30;   //将数字字转换为数值
            num += temp * int(pow(10, n - 1 - i));
        }
    }

    if (isNegative)
         return (0 - num);
    else
        return num;
}
```

[![复制代码](https://common.cnblogs.com/images/copycode.gif)](javascript:void(0);)

 

## 257.C语言的结构体和C++的有什么区别?

- C语言的结构体中是不能有函数成员的,而C++的类可以有;
- C语言的结构体中数据成员是没有private、public和protected访问限定的,而C++的类成员有这些访问限定;
- C语言的结构体是没有继承关系的,而C++的类却有丰富的继承关系。

注意:虽然C的结构体和C++的类有很大的相似度,但是类是实现面向对象的基础,而结构体只可以简单地理解为类的前身。

 

## 258.简述指针常量与常量指针的区别?

- 指针常量是指定义了一个指针,这个指针的值只能在定义时初始化,其它地方不能改变,常量指针是指定义了一个指针,这个指针指向一个只读的对象,不能通过常量指针来改变这个对象的值;
- 指针常量调调的是指针的不可变性,而常量指针强调的是指针对其所值对象的不可改变性。

注意:无论是指针常量还是常量指针,其最大的用途就是作为函数的形式参数,保证实参在被调用函数中的不可改变特性。

 

## 259.如何避免"野指针"?

- 指针变量声明时没有被初始化,解决办法为,指针声明时初始化,初始值可以是具体的地址值,也可以让它指向NULL;
- 指针p被free或者delete之后,没有设置为NULL,解决办法为,指针指向的内存空间被释放后指针应该指向NULL;
- 指针操作超越了变量的作用范围,解决办法为,在变量的作用域结束前释放掉变量的地址空间,并且让指针指向NULL。

 

## 260.句柄和指针的区别和联系是什么?

句柄和指针其实是两个截然不同的概念,Windows系统用句柄标记系统资源,隐藏系统的信息,只要知道有这个东西,然后去调用就行了,是32bit的uint。指针则标记某个内存地址,两者是不同的概念。

 

## 261.new/delete与malloc/free的区别是什么?

- new能自动计算需要分配的内存空间,而malloc需要手工计算字节数;

```
int *p = new int[2];
int *q = (int *)malloc(2 * sizeof(int));
```

- new与delete直接带具体类型的指针,malloc与free返回void类型的指针;
- new类型是安全的,而malloc不是,例如,int * p = new float[2];就会报错,而int *p = malloc(2 * sizeof(float));编译时编译器无法指出错误来;
- new一般分为两步,new操作和构造,new操作对应与malloc,但new操作可以重载,可以自定义内存分配策略,步做内存分配,甚至分配到非内存设备上,而malloc步行;
- new调用构造函数,malloc不会调用构造函数,delete调用析构函数,而free不会调用析构函数;
- malloc/free需要库文件stdlib.h支持,而new/delete则不需要。

注意:delete和free被调用后,内存不会立即回收,指针也不会指向空,delete或free仅仅是告诉操作系统,这一块内存被释放了,可以用作其它用途,但是由于没有重新对这块内存进行写操作,所以内存中的变量数值并没有发生变化,出现野指针的情况,因此,释放完内存后,应该将该指针指向NULL。

 

## 262.请说一说extern "C"?

extern "C"的主要作用就是为了能够正确实现C++代码调用其它C语言代码,加上extern "C"后,会指示编译器这部分代码按C语言的方式进行编译,而不是C++。由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名,而C语言不支持函数重载,因此编译C语言代码的函数时,不会带上函数的参数类型,一般只包括函数名。

该功能十分有用处,因为在C++出现以前,很多代码都是C语言编写的,而且很底层的库也是C语言写的,为了更好地支持原来的C代码和已经写好的C语言库,需要在C++中尽可能地支持C,而extern "C"就是其中的一个策略。

- C++代码调用C语言代码;
- 在C++的头文件中使用;
- 在多个人的协同开发时,可能有人擅长C语言,有的人擅长C++,在这样的情况下也会用到。

 

## 263.请说一说C++中struct和class的区别是什么?

在C++中,class和struct做类型定义时只有两点区别:

- 默认的继承权限不同,class默认继承权限是private继承,而struct默认是public继承;
- class还可用于定义模板参数,像typename,但是关键字struct不能定义模板参数。

C++之所以保留struct关键字,原因是:

- 保证与C语言的向下兼容性,C++必须提供一个struct;
- C++中的struct定义必须百分百地保证与C语言中的sruct的向下兼容性,把C++中的最基本的对象单元规定为class而不是struct,就是为了避免各种兼容性要求的限制;
- 对struct定义的拓展性使C语言的代码能够更容易地移植到C++中。

原文作者:[liangliangge](https://home.cnblogs.com/u/liangliangge/)

原文链接:https://www.cnblogs.com/liangliangge/p/14397918.html

## **264.new、delete、malloc、free关系**

delete会调用对象的析构函数,和new对应free只会释放内存,new调用构造函数。malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。

## **265.delete与 delete []区别**

delete只会调用一次析构函数,而delete[]会调用每一个成员的析构函数。在More Effective C++中有更为详细的解释:“当delete操作符用于数组时,它为每个数组元素调用析构函数,然后调用operator delete来释放内存。”delete与new配套,delete []与new []配套

MemTest *mTest1=new MemTest[10];

MemTest *mTest2=new MemTest;

Int *pInt1=new int [10];

Int *pInt2=new int;

delete[]pInt1; //-1-

delete[]pInt2; //-2-

delete[]mTest1;//-3-

delete[]mTest2;//-4-

在-4-处报错。

这就说明:对于内建简单数据类型,delete和delete[]功能是相同的。对于自定义的复杂数据类型,delete和delete[]不能互用。delete[]删除一个数组,delete删除一个指针。简单来说,用new分配的内存用delete删除;用new[]分配的内存用delete[]删除。delete[]会调用数组元素的析构函数。内部数据类型没有析构函数,所以问题不大。如果你在用delete时没用括号,delete就会认为指向的是单个对象,否则,它就会认为指向的是一个数组。

## **266.C++有哪些性质(面向对象特点)**

封装,继承和多态。

## **267.子类析构时要调用父类的析构函数吗?**

析构函数调用的次序是先派生类的析构后基类的析构,也就是说在基类的的析构调用的时候,派生类的信息已经全部销毁了。定义一个对象时先调用基类的构造函数、然后调用派生类的构造函数;析构的时候恰好相反:先调用派生类的析构函数、然后调用基类的析构函数。

## **268.多态,虚函数,纯虚函数**

多态:是对于不同对象接收相同消息时产生不同的动作。C++的多态性具体体现在运行和编译两个方面:在程序运行时的多态性通过继承和虚函数来体现;

在程序编译时多态性体现在函数和运算符的重载上;

虚函数:在基类中冠以关键字 virtual 的成员函数。 它提供了一种接口界面。允许在派生类中对基类的虚函数重新定义。

纯虚函数的作用:在基类中为其派生类保留一个函数的名字,以便派生类根据需要对它进行定义。作为接口而存在 纯虚函数不具备函数的功能,一般不能直接被调用。

从基类继承来的纯虚函数,在派生类中仍是虚函数。如果一个类中至少有一个纯虚函数,那么这个类被称为抽象类(abstract class)。

抽象类中不仅包括纯虚函数,也可包括虚函数。抽象类必须用作派生其他类的基类,而不能用于直接创建对象实例。但仍可使用指向抽象类的指针支持运行时多态性。

## **269.求下面函数的返回值(微软)**

```
int func(x)

{

int countx = 0;

while(x)

{

countx ++;

x = x&(x-1);

}

return countx;

}
```

假定x = 9999。 答案:8

思路:将x转化为2进制,看含有的1的个数。

## **270.什么是“引用”?申明和使用“引用”要注意哪些问题?**

答:引用就是某个目标变量的“别名”(alias),对应用的操作与对变量直接操作效果完全相同。申明一个引用的时候,切记要对其进行初始化。引用声明完毕后,相当于目标变量名有两个名称,即该目标原名称和引用名,不能再把该引用名作为其他变量名的别名。声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。不能建立数组的引用。

## **271.将“引用”作为函数参数有哪些特点?**

(1)传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。

(2)使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。

(3)使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。

## **272.在什么时候需要使用“常引用”?**

如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。常引用声明方式:const 类型标识符 &引用名=目标变量名;

例1

int a ;

const int &ra=a;

ra=1; //错误

a=1; //正确

例2

string foo( );

void bar(string & s);

那么下面的表达式将是非法的:

bar(foo( ));

bar("hello world");

原因在于foo( )和"hello world"串都会产生一个临时对象,而在C++中,这些临时对象都是const类型的。因此上面的表达式就是试图将一个const类型的对象转换为非const类型,这是非法的。引用型参数应该在能被定义为const的情况下,尽量定义为const 。

##  

**10.将“引用”作为函数返回值类型的格式、好处和需要遵守的规则?**

格式:类型标识符 &函数名(形参列表及类型说明){ //函数体 }

好处:在内存中不产生被返回值的副本;(注意:正是因为这点原因,所以返回一个局部变量的引用是不可取的。因为随着该局部变量生存期的结束,相应的引用也会失效,产生runtime error!

注意事项:

(1)不能返回局部变量的引用。这条可以参照Effective C++[1]的Item 31。主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。

(2)不能返回函数内部new分配的内存的引用。这条可以参照Effective C++[1]的Item 31。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。

(3)可以返回类成员的引用,但最好是const。这条原则可以参照Effective C++[1]的Item 30。主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。

(4)流操作符重载返回值申明为“引用”的作用:

流操作符<<和>>,这两个操作符常常希望被连续使用,例如:cout << "hello" << endl; 因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。可选的其它方案包括:返回一个流对象和返回一个流对象指针。但是对于返回一个流对象,程序必须重新(拷贝)构造一个新的流对象,也就是说,连续的两个<<操作符实际上是针对不同对象的!这无法让人接受。对于返回一个流指针则不能连续使用<<操作符。因此,返回一个流对象引用是惟一选择。这个唯一选择很关键,它说明了引用的重要性以及无可替代性,也许这就是C++语言中引入引用这个概念的原因吧。

赋值操作符=。这个操作符象流操作符一样,是可以连续使用的,例如:x = j = 10;或者(x=10)=100;赋值操作符的返回值必须是一个左值,以便可以被继续赋值。因此引用成了这个操作符的惟一返回值选择。

#include<iostream.h>

int &put(int n);

int vals[10];

int error=-1;

void main()

{

put(0)=10; //以put(0)函数值作为左值,等价于vals[0]=10;

put(9)=20; //以put(9)函数值作为左值,等价于vals[9]=20;

cout<<vals[0];

cout<<vals[9];

}

int &put(int n)

{

if (n>=0 && n<=9 ) return vals[n];

else { cout<<"subscript error"; return error; }

}

(5)在另外的一些操作符中,却千万不能返回引用:+-*/ 四则运算符。它们不能返回引用,Effective C++[1]的Item23详细的讨论了这个问题。主要原因是这四个操作符没有side effect,因此,它们必须构造一个对象作为返回值,可选的方案包括:返回一个对象、返回一个局部变量的引用,返回一个new分配的对象的引用、返回一个静态对象引用。根据前面提到的引用作为返回值的三个规则,2、3两个方案都被否决了。静态对象的引用又因为((a+b) == (c+d))会永远为true而导致错误。所以可选的只剩下返回一个对象了。

## **273.结构与联合有和区别?**

(1). 结构和联合都是由多个不同的数据类型成员组成, 但在任何同一时刻, 联合中只存放了一个被选中的成员(所有成员共用一块地址空间), 而结构的所有成员都存在(不同成员的存放地址不同)。

(2). 对于联合的不同成员赋值, 将会对其它成员重写, 原来成员的值就不存在了, 而对于结构的不同成员赋值是互不影响的。

## **274.试写出程序结果:**

int a=4;

int &f(int x)

{ a=a+x;

return a;

}

int main(void)

{ int t=5;

cout<<f(t)<<endl; a = 9

f(t)=20; a = 20

cout<<f(t)<<endl; t = 5,a = 20 a = 25

t=f(t); a = 30 t = 30

cout<<f(t)<<endl; } t = 60

}

## **275.重载(overload)和重写(overried,有的书也叫做“覆盖”)的区别?**

常考的题目。从定义上来说:

重载:是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。

重写:是指子类重新定义父类虚函数的方法。

从实现原理上来说:

重载:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这样的)。如,有两个同名函数:function func(p:integer):integer;和function func(p:string):integer;。那么编译器做过修饰后的函数名称可能是这样的:int_func、str_func。对于这两个函数的调用,在编译器间就已经确定了,是静态的。也就是说,它们的地址在编译期就绑定了(早绑定),因此,重载和多态无关!

重写:和多态真正相关。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚绑定)。

## **276.有哪几种情况只能用intialization list 而不能用assignment?**

答案:当类中含有const、reference 成员变量;基类的构造函数都需要初始化表。

## **277. C++是不是类型安全的?**

答案:不是。两个不同类型的指针之间可以强制转换(用reinterpret cast)。C#是类型安全的。

## **278. main 函数执行以前,还会执行什么代码?**

答案:全局对象的构造函数会在main 函数之前执行。

## **279. 描述内存分配方式以及它们的区别?**

1) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量。

2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集。

3) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc 或new 申请任意多少的内存,程序员自己负责在何时用free 或delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活,但问题也最多。

## **280.分别写出BOOL,int,float,指针类型的变量a 与“零”的比较语句。**

答案:

BOOL : if ( !a ) or if(a)

int : if ( a == 0)

float : const EXPRESSION EXP = 0.000001

if ( a < EXP && a >-EXP)

pointer : if ( a != NULL) or if(a == NULL)

## **281.请说出const与#define 相比,有何优点?**

答案:

const作用:定义常量、修饰函数参数、修饰函数返回值三个作用。被Const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。

1) const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。

2) 有些集成化的调试工具可以对const 常量进行调试,但是不能对宏常量进行调试。

## **282.简述数组与指针的区别?**

数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。指针可以随时指向任意类型的内存块。

(1)修改内容上的差别

char a[] = “hello”;

a[0] = ‘X’;

char *p = “world”; // 注意p 指向常量字符串

p[0] = ‘X’; // 编译器不能发现该错误,运行时错误

(2) 用运算符sizeof 可以计算出数组的容量(字节数)。sizeof(p),p 为指针得到的是一个指针变量的字节数,而不是p 所指的内存容量。C++/C 语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。

char a[] = "hello world";

char *p = a;

cout<< sizeof(a) << endl; // 12 字节

cout<< sizeof(p) << endl; // 4 字节

计算数组和指针的内存容量

void Func(char a[100])

{

cout<< sizeof(a) << endl; // 4 字节而不是100 字节

}

## **283. int (\*s[10])(int) 表示的是什么?**

int (*s[10])(int) 函数指针数组,每个指针指向一个int func(int param)的函数。

## **284.栈内存与文字常量区**

char str1[] = "abc";

char str2[] = "abc";

const char str3[] = "abc";

const char str4[] = "abc";

const char *str5 = "abc";

const char *str6 = "abc";

char *str7 = "abc";

char *str8 = "abc";

cout << ( str1 == str2 ) << endl;//0 分别指向各自的栈内存

cout << ( str3 == str4 ) << endl;//0 分别指向各自的栈内存

cout << ( str5 == str6 ) << endl;//1指向文字常量区地址相同

cout << ( str7 == str8 ) << endl;//1指向文字常量区地址相同

结果是:0 0 1 1

解答:str1,str2,str3,str4是数组变量,它们有各自的内存空间;而str5,str6,str7,str8是指针,它们指向相同的常量区域。

## **285.将程序跳转到指定内存地址**

要对绝对地址0x100000赋值,我们可以用(unsigned int*)0x100000 = 1234;那么要是想让程序跳转到绝对地址是0x100000去执行,应该怎么做?

*((void (*)( ))0x100000 ) ( );

首先要将0x100000强制转换成函数指针,即:

(void (*)())0x100000

然后再调用它:

*((void (*)())0x100000)();

用typedef可以看得更直观些:

typedef void(*)() voidFuncPtr;

*((voidFuncPtr)0x100000)();

## **286.int id[sizeof(unsigned long)];这个对吗?为什么?**

答案:正确 这个 sizeof是编译时运算符,编译时就确定了 ,可以看成和机器有关的常量。

## **287.引用与指针有什么区别?**

【参考答案】

\1) 引用必须被初始化,指针不必。

\2) 引用初始化以后不能被改变,指针可以改变所指的对象。

\3) 不存在指向空值的引用,但是存在指向空值的指针。

## **288.const 与 #define 的比较 ,const有什么优点?**

【参考答案】

(1) const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误(边际效应) 。

(2) 有些集成化的调试工具可以对 const 常量进行调试,但是不能对宏常量进行调试。

27题:复杂声明

void * ( * (*fp1)(int))[10];

float (*(* fp2)(int,int,int))(int);

int (* ( * fp3)())[10]();

分别表示什么意思?

【标准答案】

1.void * ( * (*fp1)(int))[10]; fp1是一个指针,指向一个函数,这个函数的参数为int型,函数的返回值是一个指针,这个指针指向一个数组,这个数组有10个元素,每个元素是一个void*型指针。

2.float (*(* fp2)(int,int,int))(int); fp2是一个指针,指向一个函数,这个函数的参数为3个int型,函数的返回值是一个指针,这个指针指向一个函数,这个函数的参数为int型,函数的返回值是float型。

[http://3.int](https://link.zhihu.com/?target=http%3A//3.int) (* ( * fp3)())[10](); fp3是一个指针,指向一个函数,这个函数的参数为空,函数的返回值是一个指针,这个指针指向一个数组,这个数组有10个元素,每个元素是一个指针,指向一个函数,这个函数的参数为空,函数的返回值是int型。

## **289.内存的分配方式有几种?**

【参考答案】

一、从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量。

二、在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

三、从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。

## **290.基类的析构函数不是虚函数,会带来什么问题?**

【参考答案】派生类的析构函数用不上,会造成资源的泄漏。

## **291.全局变量和局部变量有什么区别?是怎么实现的?操作系统和编译器是怎么知道的?**

【参考答案】

生命周期不同:

全局变量随主程序创建和创建,随主程序销毁而销毁;局部变量在局部函数内部,甚至局部循环体等内部存在,退出就不存在;

使用方式不同:通过声明后全局变量程序的各个部分都可以用到;局部变量只能在局部使用;分配在栈区。

操作系统和编译器通过内存分配的位置来知道的,全局变量分配在全局数据段并且在程序开始运行的时候被加载。局部变量则分配在堆栈里面 。

原文作者:[Joey Seung](https://www.zhihu.com/people/joey-seung-64)

原文链接:https://zhuanlan.zhihu.com/p/75347892?utm_source=wechat_session

## 292. **const关键字(反义词mutable)**

(1)定义时必须初始化

(2)指针可以是const指针,也可以是指向const对象的指针

(3)定义为const的形参,在函数体内不能被修改

(4)后面加Const,表示该成员函数不会修改类的成员变量。本质是修饰隐藏的*this指针。加const的成员函数可以被const或非const对象调用,但是普通成员函数(无const修饰)只能被普通对象(无const修饰)调用。

(5)前面加const,表示返回值是const类型的

(6)Const修饰成员变量时,不能在声明时初始化,必须在构造函数的列表里初始化

## 293. **static关键字**

(1)在函数中,一个static的变量在此函数被调用过程中维持其值不变

(2)在模块中(不在函数中),一个static变量可以被模块中所有函数访问,但不可以被模块外的其他函数访问。

(3)在模块内,一个static的函数只可以被这一模块内的其他函数调用。

(4)类中的static成员变量属于整个类,不能在类内进行定义,只能在类的作用域中进行定义。

(5)类中的static成员函数属于整个类,不包含this指针,只能调用static成员函数。

(6)static全局变量只能在本文件中使用,限制了它的作用域;而普通全局变量可以在其他文件中使用。

(7)static局部变量必须初始化,普通局部变量不需要;前者所在的函数被多次调用时,依据上一次的结果进行计算,而后者所在的函数被调用时,还是原来的值。虽然静态局部变量在函数调用结束后仍然存在,但其他函数不能引用它。

(8)static函数限定在本文件中使用,虽然其他文件可以知道它的存在,但不能使用;而普通函数默认是extern的,其他文件也可以使用。Static函数有两个好处:一是其他文件可以定义相同名字的函数,不会冲突;二是静态函数不能为其他函数使用。

## 294. **extern关键字**

(1)extern C,表示该段代码以C语言进行编译。

(2)extern 放在变量或函数前,说明该变量或函数定义在别的文件中,提示编译器去其他模块中找定义,相当于前向声明。

## 295. **指针和引用的区别**

(1)引用是直接访问,指针是间接访问。

(2)引用是变量的别名,本身不单独分配自己的内存空间,而指针有自己的内存空间

(3)引用绑定内存空间(必须赋初值),是一个变量别名不能更改绑定,可以改变对象的值。

总的来说:引用既具有指针的效率,又具有变量使用的方便性和直观性

## **296.explicit是干什么用的 ?**

声明为explicit的构造函数不能在隐式转换中使用。可以阻止不应该允许的经过[转换构造函数]进行的隐式转换的发生。

## **297.**inline的用法

https://www.cnblogs.com/fnlingnzb-learner/p/6423917.html

## 298. **new/delete与malloc/free之间的区别?**

(1)malloc/free是C/C++语言的标准库函数,new/delete是C++的运算符
(2)new能够自动分配空间大小,malloc传入参数。
(3)new/delete能进行对对象进行构造和析构函数的调用进而对内存进行更加详细的工作,而malloc/free不能。
既然new/delete的功能完全覆盖了malloc/free,为什么C++还保留malloc/free呢?因为C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。

## 299. **浅拷贝与深拷贝?为什么要使用深拷贝?**

(1)浅拷贝 char * arr[] = “hello”; char * a = arr;浅拷贝只是对指针的拷贝,拷贝后两个指针指向同一内存空间。

(2)深拷贝 char * arr[] = “hello”; char * a = new char[]; a =arr; 深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经过深拷贝后的指针是指向两个不同地址的指针。

浅拷贝会出现的问题:(1)浅拷贝只是拷贝了指针,使得两个指针指向同一地址,这样在对象结束调用析构函数时,会造成同一份资源析构两次,即delete同一块内存两次,造成程序崩溃;(2)浅拷贝使得两个指针指向同一个地址,任何一方的改动都会影响另一方;(3)同一个空间,第二次释放失败,导师无法操作该空间,造成内存泄漏。

## 300.深入谈谈堆和栈?

(1)分配和管理方式不同 :
    堆是动态分配的,其空间的分配和释放都由程序员控制。
   栈由编译器自动管理。栈有两种分配方式:静态分配和动态分配。静态分配由编译器完成,比如局部变量的分配。动态分配由alloca()函数进行分配,但是栈的动态分配和堆是不同的,它的动态分配是由编译器进行释放,无须手工控制。
(2)产生碎片不同
    对堆来说,频繁的new/delete或者malloc/free势必会造成内存空间的不连续,造成大量的碎片,使程序效率降低。
    对栈而言,则不存在碎片问题,因为栈是先进后出的队列,永远不可能有一个内存块从栈中间弹出。
(3)生长方向不同
   堆是向着内存地址增加的方向增长的,从内存的低地址向高地址方向增长。
   栈是向着内存地址减小的方向增长,由内存的高地址向低地址方向增长。

## **301.内存的静态分配和动态分配的区别?**

(1)时间不同。静态分配发生在程序编译和连接时。动态分配则发生在程序调入和执行时。
(2)空间不同。堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。alloca,可以从栈里动态分配内存,不用担心内存泄露问题,当函数返回时,通过alloca申请的内存就会被自动释放掉。

## 302. **实现string类**

```
class` `string``{``public``:``String()  ``//初始化``: _pstr(``new` `char``[1])``{}` `String(``const` `char` `* pstr );   ``//普通构造函数``: _pstr(``new` `char``[``strlen``(pstr) + 1]())``{``  ``strcpy``(_pstr,pstr);``}` `String(``const` `String & rhs); ``//复制构造函数``: _pstr(``new` `char``[``strlen``(pstr) + 1]())``{``  ``strcpy``(_pstr, rhs.pstr);``}` `String(String && rhs); ``//移动构造函数,右值引用``: _pstr(rhs._pstr)``{``  rhs.pstr = NULL;``}` `String & operator=(``const` `String & rhs) ``//重载复制运算符函数``{``  ``if``(``this` `!= & rhs)``  {``    ``delete` `[] _pstr;``    _pstr = ``new` `char``[``strlen``(rhs._pstr) + 1]();``    ``strcpy``(_pstr, rhs._pstr);``  }``  ``return` `*``this``;``}``String & operator=(String && rhs) ``//移动赋值运算符函数``{``  ``if``(``this` `!= &rhs)``  {``    ``delete` `[] _pstr;``    _pstr = rhs._pstr;``    rhs._pstr = NULL;``  }``  ``return` `* ``this``;``}` `~String()``{``  ``delete` `[] _pstr;``}` `  ``friend` `std::ostream &operator<<(std::ostream & os, ``const` `String & rhs);``private``:``  ``char` `* _pstr;``};` `std::ostream & operator<<(std::ostream & os, ``const` `String & rhs)``{``  os << rhs._pstr;``  ``return` `os;``}
```

## 303. **什么是继承?什么是多态?**

(1)

(2)C++中多态机制主要体现在两个方面,一个是函数的重载,一个是接口的重写。接口多态指的是“一个接口多种形态”。每一个对象内部都有一个虚表指针,该虚表指针被初始化为本类的虚表。所以在程序中,不管你的对象类型如何转换,但该对象内部的虚表指针是固定的,所以呢,才能实现动态的对象函数调用,这就是C++多态性实现的原理。

多态的基础是继承,需要虚函数的支持,简单的多态是很简单的。子类继承父类大部分的资源,不能继承的有构造函数,析构函数,拷贝构造函数,operator=函数,友元函数等等

多态作用:

隐藏实现细节,代码能够模块化;2. 接口重用:为了类在继承和派生的时候正确调用。

多态的两个必要条件:

\1. 一个基类的指针或者引用指向派生类的对象;2.虚函数

 

## 304. **什么是静态关联?什么是动态关联?**

静态关联是程序在编译阶段就能确定实际执行动作,程序运行时才能确定执行的动作叫动态关联。

## 305. **虚函数是如何实现的?**

编译时若基类中有虚函数,编译器为该的类创建一个一维数组的虚表,存放是每个虚函数的地址。基类和派生类都包含虚函数时,这两个类都建立一个虚表。构造函数中进行虚表的创建和虚表指针的初始化。在构造子类对象时,要先调用父类的构造函数,初始化父类对象的虚表指针,该虚表指针指向父类的虚表。执行子类的构造函数时,子类对象的虚表指针被初始化,指向自身的虚表。每一个类都有虚表。虚表可以继承,如果子类没有重写虚函数,那么子类虚表中仍然会有该函数的地址,只不过这个地址指向的是基类的虚函数实现。派生类的虚表中虚函数地址的排列顺序和基类的虚表中虚函数地址排列顺序相同。当用一个指针/引用调用一个函数的时候,被调用的函数是取决于这个指针/引用的类型。即如果这个指针/引用是基类对象的指针/引用就调用基类的方法;如果指针/引用是派生类对象的指针/引用就调用派生类的方法,当然如果派生类中没有此方法,就会向上到基类里面去寻找相应的方法。这些调用在编译阶段就确定了。当涉及到多态性的时候,采用了虚函数和动态绑定,此时的调用就不会在编译时候确定而是在运行时确定。不在单独考虑指针/引用的类型而是看指针/引用的对象的类型来判断函数的调用,根据对象中虚指针指向的虚表中的函数的地址来确定调用哪个函数。

## 306. **虚函数与纯虚函数的区别?含有纯虚函数的类叫什么?**

(1)虚函数与纯虚函数都可以在子类中重写。

(2)纯虚函数只有定义,没有实现;虚函数既要有定义,也要有实现的代码。

(3)纯虚函数 vritual void print() = 0; 虚函数 vritual void print() {  XXX  };

(4)包含纯虚函数的类叫抽象类,该类不可以创建对象;而含有虚函数的类可以创建对象。

## 307. **多重继承如何解决?**

虚拟继承解决了多重继承的问题。如:A是基类,B、C继承自A,D多重继承自B和C,那么D访问A中的变量时,就会出现二义性错误。如果类B和类C虚拟继承自A,那么类D只会有A的一个对象,这样就解决了二义性问题。或者用成员限定符解决二义性。

## 308. **派生类与虚函数概述**

(1)派生类继承的函数不能定义为虚函数。虚函数是希望派生类重新定义。如果派生类没有重新定义某个虚函数,则在调用的时候会使用基类中定义的版本。

(2)派生类中函数的声明必须与基类中定义的方式完全匹配。

(3)基类中声明为虚函数,则派生类也为虚函数。

## 309. **为什么析构函数要定义为虚函数?哪些函数不能是虚函数?**

(1)如果析构函数不是虚函数,那么释放内存时候,编译器会使用静态联编,认为p就是一个基类指针,调用基类析构函数,这样子类对象的内存没有释放,造成内存泄漏。定义成虚函数以后,就会动态联编,先调用子类析构函数,再基类。
(2)1)普通函数只能重载,不能被重写,因此编译器会在编译时绑定函数。
2)构造函数是知道全部信息才能创建对象,然而虚函数允许只知道部分信息。
3)内联函数在编译时被展开,虚函数在运行时才能动态绑定函数。
4)友元函数 因为不可以被继承。
5)静态成员函数 只有一个实体,不能被继承。父类和子类共有。

## 310. **析构函数可以抛出异常吗?为什么不能抛出异常?除了资源泄露,还有其他需考虑的因素吗?**

C++标准指明析构函数不能、也不应该抛出异常。C++异常处理模型最大的特点和优势就是对C++中的面向对象提供了最强大的无缝支持。那么如果对象在运行期间出现了异常,C++异常处理模型有责任清除那些由于出现异常所导致的已经失效了的对象(也即对象超出了它原来的作用域),并释放对象原来所分配的资源, 这就是调用这些对象的析构函数来完成释放资源的任务,所以从这个意义上说,析构函数已经变成了异常处理的一部分。

1)如果析构函数抛出异常,则异常点之后的程序不会执行,如果析构函数在异常点之后执行了某些必要的动作比如释放某些资源,则这些动作不会执行,会造成诸如资源泄漏的问题。

2)通常异常发生时,c++的机制会调用已经构造对象的析构函数来释放资源,此时若析构函数本身也抛出异常,则前一个异常尚未处理,又有新的异常,会造成程序崩溃的问题。

## 311.动态链接库的两种使用方法及特点?

1).载入时动态链接,模块非常明确调用某个导出函数,使得他们就像本地函数一样。这需要链接时链接那些函数所在DLL的导入库,导入库向系统提供了载入DLL时所需的信息及DLL函数定位。 

2)运行时动态链接。

## 312. **STL各类容器(3个顺序+4个关联+1个无序关联)的实现原理及使用情形**

(1)vector:可变数组大小。支持快速随机访问。在尾部之外的位置插入或删除元素可能很慢。

(2)deque:双端队列。支持快速随机访问。在头尾插入或删除碎度很快。

(3)list:双向链表。只支持双向顺序访问。在list的任何位置进行插入或删除操作速度都很快。

(4)set/multiset:只有键值,可以把set当做集合使用。multiset可以存放相同的元素,set只能存放不同的元素。

(5)map/multimap:键值对,每一个元素都是pair,pair的第一个元素是关键字,第二个元素是值。这两者的区别就在于multimap可以存放多个相同的关键字,map则不可以。

(3)与(5)的底层实现都是红黑树,动态平衡二叉树。插入和删除等操作的时间复杂度是O(logn)(6)中的底层实现是哈希函数。

(6)unordered_map 映射 
unordered_multimap 多重映射 
unordered_set 集合 
unordered_multiset 多重集合

## 313.什么是STL?

 六大组件:容器、迭代器、适配器、算法、函数对象、配置器(透明)

(1)容器(略,自己看)

(2)迭代器:随机访问迭代器(Random Access Iterator)

双向迭代器(Bidirectional Iterator)

前向迭代器(Forward Iterator)

输入迭代器(Input Iterator)

输出迭代器(Output Iterator)

(3)适配器就是Interface(接口),对容器、迭代器和算法进行包装,但其实质还是容器、迭代器和算法,只是不依赖于具体的标准容器、迭代器和算法类型,容器适配器可以理解为容器的模板,迭代器适配器可理解为迭代器的模板,算法适配器可理解为算法的模板。

常见的容器适配器有:stack、queue、priority_queue(不支持迭代器访问)

前面简要提到了适配器的概念,适配器相当于提供了一个接口,使得某些不适用于特定对象的方法可以被该对象所用,适配器形象的功能图解如所示,图中,容器或函数对象无法直接应用于算法,因此,必须有一种中间过渡机制来实现两者的匹配,这就是适配器,本质上,适配器是使一事物的行为类似于另一事物的行为的一种机制。

(4)STL将算法库分为4组,前3个在algorithm头文件中描述,而第4个在numeric头文件中描述:

非修改式序列操作:不改变容器的内容,如find()、for_each()等。

修改式序列操作:可以修改容器中的内容,如transform()、random_shuffle()、copy等。

排序和相关操作:包括各种排序函数等,如sort()等。

通用数字运算:计算两个容器的内部乘积等。

(5)函数对象是可以以函数方式与()结合使用的任意对象,包括:(functor-仿函数)

函数名;指向函数的指针;重载了()操作符的类对象(即定义了函数operator()()的类)。

(6)一级配置器和二级配置器

空间配置器,就是用来配置、管理和释放空间的,给所有的容器包括算法提供生存空间。

作用:

(1)提高代码复用率,功能模块化。 
(2)减少内存碎片问题。 
(3)提高内存分配的效率。 
(4)有内存不足时的应对措施。 
(5)隐藏实际中对存储空间的分配及释放细节,确保所有被分配的存储空间都最终获得释放。 
(5)考虑多线程状态。

考虑到小型区块可能导致的内存碎片问题,设置了两级空间配置器。分别为:一级空间配置器、二级空间配置器。当区块大于128字节,调用一级空间配置器;小于等于128字节,为了降低额外开销,用底层较复杂的二级空间配置器。

 

一级空间配置器

用malloc()、free()、realloc()等C函数执行内存配置、释放、重配置操作,并实现出类似的C++new_hanle的机制

 

二级空间配置器

SGI二级空间配置器的原理是:当区块小于128字节,则以内存池(memory pool)管理,回收时管理一个用户归还的空间,类似于哈希桶。每次配置一块内存,并维护对应的自由链表(free_list)。为了方便管理,SGI二级配置器会对齐到8个字节。(例:需要30字节的空间,自动调整到32字节)。维护16个free_lists,各自管理大小分别为 
8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128字节。 

## 314. **什么是智能指针?底层实现?**

(1)C++11中引入了智能指针的概念,方便管理堆内存。使用普通指针,容易造成堆内存泄露(忘记释放),二次释放,程序发生异常时内存泄露等问题等,使用智能指针能更好的管理堆内存。

(2)理解智能指针需要从下面三个层次:

从较浅的层面看,智能指针是利用了一种叫做RAII(资源获取即初始化)的技术对普通的指针进行封装,这使得智能指针实质是一个对象,行为表现的却像一个指针。

智能指针的作用是防止忘记调用delete释放内存和程序异常的进入catch块忘记释放内存。另外指针的释放时机也是非常有考究的,多次释放同一个指针会造成程序崩溃,这些都可以通过智能指针来解决。

智能指针还有一个作用是把值语义转换成引用语义。

(3)智能指针#include<memory>,unique_ptr,shared_ptr,weak_ptr(弱引用智能指针)。

(4)unique_ptr“唯一”拥有其所指对象,同一时刻只能有一个unique_ptr指向给定对象(通过禁止拷贝语义、只有移动语义来实现)。相比与原始指针unique_ptr用于其RAII的特性,使得在出现异常的情况下,动态资源能得到释放。unique_ptr指针本身的生命周期:从unique_ptr指针创建时开始,直到离开作用域。离开作用域时,若其指向对象,则将其所指对象销毁(默认使用delete操作符,用户可指定其他操作)。

(5)shared_ptr多个指针指向相同的对象。shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。每使用他一次,内部的引用计数加1,每析构一次,内部的引用计数减1,减为0时,自动删除所指向的堆内存。shared_ptr内部的引用计数是线程安全的,但是对象的读取需要加锁。

(6)weak_ptr是为了配合shared_ptr而引入的一种智能指针,因为它不具有普通指针的行为,没有重载operator*和->,它的最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况。

## 315. **多进程与多线程之间的区别?(最好要了解透彻)**

1)进程数据是分开的:共享复杂,需要用IPC,同步简单;多线程共享进程数据:共享简单,同步复杂

2)进程创建销毁、切换复杂,速度慢 ;线程创建销毁、切换简单,速度快 

3)进程占用内存多, CPU利用率低;线程占用内存少, CPU利用率高

4)进程编程简单,调试简单;线程 编程复杂,调试复杂

5)进程间不会相互影响 ;线程一个线程挂掉将导致整个进程挂掉

6)进程适应于多核、多机分布;线程适用于多核

线程所私有的:

线程id、寄存器的值、栈、线程的优先级和调度策略、线程的私有数据、信号屏蔽字、errno变量、

 

## 316. **什么是进程池和线程池?**

在面向对象程序编程中,对象的创建与析构都是一个较为复杂的过程,较费时间,所以为了提高程序的运行效率尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁。 
所以我们可以创建一个进程池(线程池),预先放一些进程(线程)进去,要用的时候就直接调用,用完之后再把进程归还给进程池,省下创建删除进程的时间,不过当然就需要额外的开销了。 
利用线程池与进程池可以使管理进程与线程的工作交给系统管理,不需要程序员对里面的线程、进程进行管理。

以进程池为例

进程池是由服务器预先创建的一组子进程,这些子进程的数目在 3~10 个之间(当然这只是典型情况)。线程池中的线程数量应该和CPU数量差不多。

进程池中的所有子进程都运行着相同的代码,并具有相同的属性,比如优先级、 PGID 等。

当有新的任务来到时,主进程将通过某种方式选择进程池中的某一个子进程来为之服务。相比于动态创建子进程,选择一个已经存在的子进程的代价显得小得多。至于主进程选择哪个子进程来为新任务服务,则有两种方法:

主进程使用某种算法来主动选择子进程。最简单、最常用的算法是随机算法和Round Robin(轮流算法)。

主进程和所有子进程通过一个共享的工作队列来同步,子进程都睡眠在该工作队列上。当有新的任务到来时,主进程将任务添加到工作队列中。这将唤醒正在等待任务的子进程,不过只有一个子进程将获得新任务的“接管权”,它可以从工作队列中取出任务并执行之,而其他子进程将继续睡眠在工作队列上。

当选择好子进程后,主进程还需要使用某种通知机制来告诉目标子进程有新任务需要处理,并传递必要的数据。最简单的方式是,在父进程和子进程之间预先建立好一条管道,然后通过管道来实现所有的进程间通信。在父线程和子线程之间传递数据就要简单得多,因为我们可以把这些数据定义为全局,那么它们本身就是被所有线程共享的。

## 317.**进程间的通信方式有哪些?如何实现的?**

信号和信号量是不同的,它们虽然都可以用来同步和互斥,但是信号是使用信号处理器来进行的,信号量是使用P,V操作来实现的。

消息队列是比较高级的一种进程间通信方式,因为它真的是可以在进程间传送message,传送普通字符串也可以。

一个消息队列可以被多个进程所共享(IPC((Inter-Process Communication,进程间通信))就是在这个基础上进行的);如果一个进程消息太多,一个消息队列放不下,也可以用多于一个的消息队列(不管管理可能会比较复杂)。共享消息队列的进程所发送的消息除了message本身外还有一个标志,这个标志可以指明该消息将由哪个进程或者哪类进程接受。每一个共享消息队列的进程针对这个队列也有自己的标志,可以用来申明自己的身份。

共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。

套接字( socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信

 

## 318. **简述inux中的同步与异步机制?**

同步:
所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。也就是必须一件一件事做,等前一件做完了才能做下一件事。

例如普通B/S模式(同步):提交请求->等待服务器处理->处理完毕返回 这个期间客户端浏览器不能干任何事

异步:
异步的概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。

例如 ajax请求(异步): 请求通过事件触发->服务器处理(这是浏览器仍然可以作其他事情)->处理完毕

## **319.简述阻塞与非阻塞?**

阻塞:
阻塞调用是指调用结果返回之前,当前线程会被挂起(线程进入非可执行状态,在这个状态下,cpu不会给线程分配时间片,即线程暂停运行)。函数只有在得到结果之后才会返回。

有人也许会把阻塞调用和同步调用等同起来,实际上他是不同的。对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回,它还会抢占cpu去执行其他逻辑,也会主动检测io是否准备好。

非阻塞
非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。

再简单点理解就是:

1. 同步,就是我调用一个功能,该功能没有结束前,我死等结果。

2. 异步,就是我调用一个功能,不需要知道该功能结果,该功能有结果后通知我(回调通知)

3. 阻塞,就是调用我(函数),我(函数)没有接收完数据或者没有得到结果之前,我不会返回。

4. 非阻塞,就是调用我(函数),我(函数)立即返回,通过select通知调用者

同步IO和异步IO的区别就在于:数据拷贝的时候进程是否阻塞

阻塞IO和非阻塞IO的区别就在于:应用程序的调用是否立即返回

综上可知,同步和异步,阻塞和非阻塞,有些混用,其实它们完全不是一回事,而且它们修饰的对象也不相同。

 

## **320.简述Linux中的5种I/O模式?**

1.阻塞I/O(blocking I/O)

2.非阻塞I/O (nonblocking I/O)

3) I/O复用(select 和poll,还有epoll) (I/O multiplexing)!!!!!(必须搞懂,超究极容易遇到)
4) 信号驱动I/O (signal driven I/O (SIGIO))
5) 异步I/O (asynchronous I/O (the POSIX aio_functions))

其中前4种都是同步,最后一种才是异步。

详情见:https://www.cnblogs.com/chaser24/p/6112071.html

## 321. **什么是死锁?四个死锁的条件?避免死锁的方法?**

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。

产生原因:竞争资源,和进程推进顺序非法

四个条件:

1)互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。

2)请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。

3)不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。

4)环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。

四种解决办法:预防(破坏死锁发四种发生条件中的一个或多个)、避免(银行家算法:如果一个进程增加的资源请求会导致死锁,则不允许此分配,记住当时算的那张矩阵图)、检测与解除

## 322. **Linux的任务调度机制是什么?**

[Linux](http://lib.csdn.net/base/linux) 分实时进程和普通进程,实时进程应该先于普通进程而运行。实时进程:

1) FIFO(先来先服务调度)

2) RR(时间片轮转调度)。

每个进程有两个优先级(动态优先级和实时优先级),实时优先级就是用来衡量实时进程是否值得运行的。 非实时进程有两种优先级,一种是静态优先级,另一种是动态优先级。实时进程又增加了第三种优先级,实时优先级。优先级越高,得到CPU时间的机会也就越大。

## **323.标准库函数与系统调用的区别?**

系统调用:是操作系统为用户态运行的进程和硬件设备(如CPU、磁盘、打印机等)进行交互提供的一组接口,即就是设置在应用程序和硬件设备之间的一个接口层。inux内核是单内核,结构紧凑,执行速度快,各个模块之间是直接调用的关系。linux系统上到下依次是用户进程->linux内核->硬件。其中系统调用接口是位于Linux内核中的,整个linux系统从上到下可以是:用户进程->系统调用接口->linux内核子系统->硬件,也就是说Linux内核包括了系统调用接口和内核子系统两部分;或者从下到上可以是:物理硬件->OS内核->OS服务->应用程序,操作系统起到“承上启下”作用,向下管理物理硬件,向上为操作系服务和应用程序提供接口,这里的接口就是系统调用了。
库函数:把函数放到库里。是把一些常用到的函数编完放到一个lib文件里,供别人用。别人用的时候把它所在的文件名用#include<>加到里面就可以了。一类是c语言标准规定的库函数,一类是编译器特定的库函数。
系统调用是为了方便使用操作系统的接口,而库函数则是为了人们编程的方便。

## 324. **分别简述三次握手与四次挥手的过程?**

三次握手:C----->SYN K

​        S------>ACK K+1 SYN J

​        C------->ACK J+1  

​        DONE!

client 的 connect  引起3次握手

server 在socket, bind, listen后,阻塞在accept,三次握手完成后,accept返回一个fd,

 

 

## 325. **tcp和udp之间的区别?**

1)基于连接与无连接

2)对系统资源的要求(TCP较多,UDP少)

3)UDP程序结构较简单

4)流模式与数据报模式

5)TCP保证数据正确性,UDP可能丢包,TCP保证数据顺序,UDP不保证

6)TCP有拥塞控制和流量控制,UDP没有

TCP提供的是面向连接、可靠的字节流服务。当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。TCP提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。

是一个简单的面向数据报的运输层协议。UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。由于UDP在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快

## 326. **select、poll、epoll之间的区别?**

https://www.cnblogs.com/Anker/p/3265058.html(参考阅读)

 

## 327. **epoll有哪些触发模式?**

**(必须非常详尽的解释水平触发和边缘触发的区别,以及边缘触发在编程中要做哪些更多的确认)**

**注意:****epoll必须深入理解,必须要张口就来,必须随心所欲说出来。**

epoll有EPOLLLT和EPOLLET两种触发模式,LT是默认的模式,ET是“高速”模式。LT模式下,只要这个fd还有数据可读,每次 epoll_wait都会返回它的事件,提醒用户程序去操作,而在ET(边缘触发)模式中,它只会提示一次,直到下次再有数据流入之前都不会再提示了,无论fd中是否还有数据可读。所以在ET模式下,read一个fd的时候一定要把它的buffer读光,也就是说一直读到read的返回值小于请求值。

也就是说在LT模式的情况下一定要确认收发的数据包的buffer是不是足够大如果收发数据包大小大于buffer的大小的时候就可能会出现数据丢失的情况。

 

## 328. **若是有大规模的数据连接,并发模型如何设计?**

Epoll+线程池(epoll可以采用libevent处理)

原文作者:[CTHON](https://home.cnblogs.com/u/cthon/)

原文链接:https://www.cnblogs.com/cthon/p/9614736.html

## 329.C和C++的区别

1)C是面向过程的语言,是一个结构化的语言,考虑如何通过一个过程对输入进行处理得到输出;C++是面向对象的语言,主要特征是“封装、继承和多态”。封装隐藏了实现细节,使得代码模块化;派生类可以继承父类的数据和方法,扩展了已经存在的模块,实现了代码重用;多态则是“一个接口,多种实现”,通过派生类重写父类的虚函数,实现了接口的重用。

2)C和C++动态管理内存的方法不一样,C是使用malloc/free,而C++除此之外还有new/delete关键字。

3)C++中有引用,C中不存在引用的概念

## 330.C++中指针和引用的区别

1)指针是一个新的变量,存储了另一个变量的地址,我们可以通过访问这个地址来修改另一个变量;

引用只是一个别名,还是变量本身,对引用的任何操作就是对变量本身进行操作,以达到修改变量的目的

2)引用只有一级,而指针可以有多级

3)指针传参的时候,还是值传递,指针本身的值不可以修改,需要通过解引用才能对指向的对象进行操作

引用传参的时候,传进来的就是变量本身,因此变量可以被修改

## 331.结构体struct和共同体union(联合)的区别

结构体:将不同类型的数据组合成一个整体,是自定义类型

共同体:不同类型的几个变量共同占用一段内存

1)结构体中的每个成员都有自己独立的地址,它们是同时存在的;

共同体中的所有成员占用同一段内存,它们不能同时存在;

2)sizeof(struct)是内存对齐后所有成员长度的总和,sizeof(union)是内存对齐后最长数据成员的长度、

结构体为什么要内存对齐呢?

1.平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常

2.硬件原因:经过内存对齐之后,CPU的内存访问速度大大提升。

## 332.#define和const的区别

1)#define定义的常量没有类型,所给出的是一个立即数;const定义的常量
Download .txt
gitextract_6ovixev9/

├── 01.阿里篇/
│   ├── .gitkeep
│   ├── 1.1.1 如何实现一个高效的单向链表逆序输出?.md
│   ├── 1.1.2 已知sqrt(2)约等于1.414,要求不用数学库,求sqrt(2)精确到小数点后10位.md
│   ├── 1.1.3 给定一个二叉搜索树(BST),找到树中第 K 小的节点.md
│   ├── 1.1.4 LRU缓存机制.md
│   ├── 1.1.5 关于epoll和select的区别,以下哪些说法是正确的.md
│   ├── 1.1.6 从innodb的索引结构分析,为什么索引的 key 长度不能太长.md
│   ├── 1.1.7 MySQL的数据如何恢复到任意时间点?.md
│   ├── 1.1.8 NFS与SMB的区别?.md
│   ├── 1.1.9 输入 ping IP 后敲回车,发包前会发生什么?.md
│   ├── 1.2.0 请解释下为什么鹿晗发布恋情的时候,微博系统会崩溃,如何解决?.md
│   ├── 1.2.1 现有一批邮件需要发送给订阅顾客,且有一个集群(集群的节点数不定,会动态扩容缩容)来负责具体的邮件发送任务,如何让系统尽快地完成发送?.md
│   ├── 1.2.2 有一批气象观测站,现需要获取这些站点的观测数据,并存储到 Hive 中。但是气象局只提供了 api 查询,每次只能查询单个观测点。那么如果能够方便快速地获取到所有的观测点的数据?.md
│   ├── 1.2.3 如何实现两金额数据相加(最多小数点两位).md
│   ├── 1.2.4 关于并行计算的一些基础开放问题.md
│   ├── 1.2.5 请计算XILINX公司VU9P芯片的算力相当于多少TOPS,给出计算过程与公式.md
│   ├── 1.2.6 一颗现代处理器,每秒大概可以执行多少条简单的MOV指令,有哪些主要的影响因素.md
│   ├── 1.2.7 请分析 MaxCompute 产品与分布式技术的关系、当前大数据计算平台类产品的市场现状和发展趋势.md
│   ├── 1.2.8 对大数据平台中的元数据管理是怎么理解的,元数据收集管理体系是怎么样的,会对大数据应用有什么样的影响.md
│   ├── 1.2.9 你理解常见如阿里,和友商大数据平台的技术体系差异以及发展趋势和技术瓶颈,在存储和计算两个方面进行概述.md
│   ├── 1.3.0 在云计算大数据处理场景中,每天运行着成千上万的任务,每个任务都要进行 IO 读写.md
│   ├── 1.3.1 最大频率栈.md
│   ├── 1.3.2 给定一个链表,删除链表的倒数第N个节点,并且返回链表的头结点.md
│   ├── 1.3.3 如果让你设计一个通用的、支持各种数据库秒级备份和恢复的系统,你会如何设计.md
│   ├── 1.3.4 如果让你来设计一个支持数据库、NOSQL 和大数据之间数据实时流动的数据流及处理的系统,你会考虑哪些问题?如何设计?.md
│   ├── 1.3.5 给定一个整数数组和一个整数,返回两个数组的索引,这两个索引指向的数字的加和等于指定的整数。需要最优的算法,分析算法的空间和时间复杂度.md
│   ├── 1.3.6 假如给你一个新产品,你将从哪些方面来保障它的质量?.md
│   ├── 1.3.7 请评估一下程序的执行结果?.md
│   ├── 1.3.8 如何测试一辆自行车.md
│   └── 1.3.9 如何判断两个链表是否相交.md
├── 02.华为篇/
│   ├── .gitkeep
│   ├── 2.1.1 static有什么用途?(请至少说明两种).md
│   ├── 2.1.2 引用与指针有什么区别?.md
│   ├── 2.1.3 描述实时系统的基本特性.md
│   ├── 2.1.4 全局变量和局部变量在内存中是否有区别?如果有,是什么区别?.md
│   ├── 2.1.5 什么是平衡二叉树?.md
│   ├── 2.1.6 堆栈溢出一般是由什么原因导致的?.md
│   ├── 2.1.7 什么函数不能声明为虚函数?.md
│   ├── 2.1.8 冒泡排序算法的时间复杂度是什么?.md
│   ├── 2.1.9. Internet采用哪种网络协议?该协议的主要层次结构?.md
│   ├── 2.2.0 IP地址的编码分为哪俩部分?.md
│   ├── 2.2.1 用户输入M,N值,从1至N开始顺序循环数数,每数到M输出该数值,直至全部输出。写出C程序.md
│   └── 2.2.2 某32位系统下, C++程序,请计算sizeof 的值.md
├── 03.百度篇/
│   ├── .gitkeep
│   ├── 3.4.8 找出数组中出现次数超过一半的数,现在有一个数组,已知一个数出现的次数超过了一半,请用O(n)的复杂度的算法找出这个数。.md
│   └── 3.4.9 找出被修改过的数字.md
├── 04.腾讯篇/
│   └── .gitkeep
├── 05.美团篇/
│   ├── .gitkeep
│   └── 5.2.0 浅复制和深复制?怎样实现深复制?.md
├── 06.头条篇/
│   ├── .gitkeep
│   └── 一棵二叉树,求最大通路长度(即最大左右子树高度之和).md
├── 07.滴滴篇/
│   └── .gitkeep
├── 08.京东篇/
│   ├── .gitkeep
│   └── 简单说一下hadoop和spark的shuffle过程.md
├── 09.MySQL篇/
│   ├── .gitkeep
│   ├── 9.1.0 主键 超键 候选键 外键.md
│   ├── 9.1.1 数据库事务的四个特性及含义.md
│   ├── 9.1.2 视图的作用,视图可以更改么?.md
│   ├── 9.1.3 drop,delete与truncate的区别.md
│   ├── 9.1.4 索引的工作原理及其种类.md
│   ├── 9.1.5 连接的种类.md
│   ├── 9.1.6 数据库范式.md
│   ├── 9.1.7 数据库优化的思路.md
│   ├── 9.1.8 存储过程与触发器的区别.md
│   └── 9.1.9 解释 SQL 的 left join 和 right join.md
├── 10.Redis篇/
│   ├── .gitkeep
│   ├── 10.1.0 使用Redis有哪些好处?.md
│   ├── 10.1.1 redis相比memcached有哪些优势?.md
│   ├── 10.1.2 redis常见性能问题和解决方案.md
│   ├── 10.1.3 MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据.md
│   ├── 10.1.3 zookeeper的四种类型的znode.md
│   ├── 10.1.4 Memcache与Redis的区别都有哪些?.md
│   ├── 10.1.5 Redis 常见的性能问题都有哪些?如何解决?.md
│   ├── 10.1.6 redis最适合的场景.md
│   ├── 10.1.7 Redis的同步机制了解么?.md
│   └── 10.1.8 是否使用过Redis集群,集群的原理是什么?.md
├── 11.MongoDB篇/
│   ├── .gitkeep
│   ├── 11.1.0 什么是MongoDB?.md
│   ├── 11.1.1 MongoDB是由哪种语言写的?.md
│   ├── 11.1.2 MongoDB的优势有哪些?.md
│   ├── 11.1.3 什么是数据库?.md
│   ├── 11.1.4 什么是集合?.md
│   ├── 11.1.5 什么是文档?.md
│   ├── 11.1.6 MongoDB和关系型数据库术语对比图.md
│   ├── 11.1.7 什么是“mongod”?.md
│   ├── 11.1.8 “mongod”参数有什么?.md
│   ├── 11.1.9 什么是“mongo”?.md
│   ├── 11.2.0 MongoDB哪个命令可以切换数据库?.md
│   ├── 11.2.1 什么是非关系型数据库?.md
│   ├── 11.2.2 非关系型数据库有哪些类型?.md
│   ├── 11.2.3 为什么用MOngoDB?.md
│   ├── 11.2.4 在哪些场景使用MongoDB?.md
│   ├── 11.2.5 MongoDB中的命名空间是什么意思.md
│   ├── 11.2.6 哪些语言支持MongoDB.md
│   ├── 11.2.7 在MongoDB中如何创建一个新的数据库?.md
│   ├── 11.2.8 在MongoDB中如何查看数据库列表?.md
│   ├── 11.2.9 MongoDB中的分片是什么意思?.md
│   ├── 11.3.0 如何查看使用MongoDB的连接?.md
│   ├── 11.3.1 什么是复制?.md
│   ├── 11.3.2 在MongoDB中如何在集合中插入一个文档?.md
│   ├── 11.3.3 在MongoDB中如何除去一个数据库?.md
│   ├── 11.3.4 在MongoDB中如何创建一个集合?.md
│   ├── 11.3.5 在MongoDB中如何查看一个已经创建的集合?.md
│   ├── 11.3.6 在MongoDB中如何删除一个集合?.md
│   ├── 11.3.7 为什么要在MongoDB中使用分析器?.md
│   ├── 11.3.8 MongoDB支持主键外键关系吗?.md
│   ├── 11.3.9 MongoDB支持哪些数据类型?.md
│   ├── 11.4.0 为什么要在MongoDB中用Code数据类型?.md
│   ├── 11.4.1 为什么要在MongoDB中用Regular Expression数据类型?.md
│   ├── 11.4.2 为什么在MongoDB中使用Object ID数据类型?.md
│   ├── 11.4.3 如何在集合中插入一个文档?.md
│   ├── 11.4.4 “ObjectID”有哪些部分组成?.md
│   ├── 11.4.5 在MongoDb中什么是索引?.md
│   ├── 11.4.6 如何添加索引?.md
│   ├── 11.4.7 MongoDB有哪些可替代产品?.md
│   ├── 11.4.8 如何查询集合中的文档?.md
│   ├── 11.4.9 用什么方法可以格式化输出结果?.md
│   ├── 11.5.0 如何使用AND或OR条件循环查询集合中的文档?.md
│   ├── 11.5.1 在MongoDB中如何更新数据?.md
│   ├── 11.5.2 如何删除文档?.md
│   ├── 11.5.3 在MongoDB中如何排序?.md
│   ├── 11.5.4 什么是聚合?.md
│   ├── 11.5.5 在MongoDB中什么是副本集?.md
│   └── 11.5.6 Mongodb存储特性与内部原理.md
├── 12.Zookeeper篇/
│   ├── .gitkeep
│   ├── 12.1.0 zookeeper是什么?.md
│   ├── 12.1.1 zookeeper提供了什么?.md
│   ├── 12.1.2 zookeeper文件系统.md
│   ├── 12.1.3 zookeeper的四种类型的znode.md
│   ├── 12.1.4 zookeeper通知机制.md
│   ├── 12.1.5 zookeeper有哪些应用场景?.md
│   ├── 12.1.6 zk的命名服务.md
│   ├── 12.1.7 zk的配置管理服务.md
│   ├── 12.1.8 zk的集群管理.md
│   ├── 12.1.9 zk的分布式锁.md
│   ├── 12.2.0 zk队列管理.md
│   ├── 12.2.1 zk数据复制.md
│   ├── 12.2.2 zk中zab的工作原理.md
│   ├── 12.2.3 zk是如何保证事务的顺序一致性.md
│   ├── 12.2.4 zk集群下server工作状态.md
│   ├── 12.2.5 zk是如何选举Leader的?.md
│   ├── 12.2.6 zk同步流程.md
│   ├── 12.2.7 分布式通知和协调.md
│   └── 12.2.8 zk的session机制.md
├── 13.Nginx篇/
│   └── .gitkeep
├── 14.算法篇/
│   └── .gitkeep
├── 15.内存篇/
│   └── .gitkeep
├── 16.CPU篇/
│   └── .gitkeep
├── 17.磁盘篇/
│   └── .gitkeep
├── 18.网络通信篇/
│   └── .gitkeep
├── 19.安全篇/
│   └── .gitkeep
├── 20.并发篇/
│   └── .gitkeep
├── 2023adding.md
├── 21.面经/
│   └── 2020秋招面经总结.md
├── README.md
└── sync_link
Condensed preview — 156 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (701K chars).
[
  {
    "path": "01.阿里篇/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "01.阿里篇/1.1.1 如何实现一个高效的单向链表逆序输出?.md",
    "chars": 1455,
    "preview": "##### **问题**:如何实现一个高效的单向链表逆序输出? \n\n##### **出题人**:阿里巴巴出题专家:昀龙/阿里云弹性人工智能负责人\n\n##### **参考答案**:下面是其中一种写法,也可以有不同的写法,比如递归等。供参考。\n"
  },
  {
    "path": "01.阿里篇/1.1.2 已知sqrt(2)约等于1.414,要求不用数学库,求sqrt(2)精确到小数点后10位.md",
    "chars": 1060,
    "preview": "#### **题目**:已知 sqrt (2)约等于 1.414,要求不用数学库,求 sqrt (2)精确到小数点后 10 位。\n#### **出题人**:——阿里巴巴出题专家:文景/阿里云 CDN 资深技术专家\n#### **参考答案**"
  },
  {
    "path": "01.阿里篇/1.1.3 给定一个二叉搜索树(BST),找到树中第 K 小的节点.md",
    "chars": 6142,
    "preview": "#### **题目**:给定一个二叉搜索树(BST),找到树中第 K 小的节点。\n#### **出题人**:阿里巴巴出题专家:文景/阿里云 CDN 资深技术专家\n#### **参考答案**:\n\n##### * 考察点\n1. 基础数据结构的理"
  },
  {
    "path": "01.阿里篇/1.1.4 LRU缓存机制.md",
    "chars": 2252,
    "preview": "**题目**:LRU 缓存机制\n设计和实现一个 LRU(最近最少使用)缓存数据结构,使它应该支持一下操作:get 和 put。\nget(key) - 如果 key 存在于缓存中,则获取 key 的 value(总是正数),否则返回 -1。\n"
  },
  {
    "path": "01.阿里篇/1.1.5 关于epoll和select的区别,以下哪些说法是正确的.md",
    "chars": 347,
    "preview": "##### **问题**:关于 epoll 和 select 的区别,哪些说法是正确的?(多选)\nA. epoll 和 select 都是 I/O 多路复用的技术,都可以实现同时监听多个 I/O 事件的状态。\n\nB. epoll 相比 se"
  },
  {
    "path": "01.阿里篇/1.1.6 从innodb的索引结构分析,为什么索引的 key 长度不能太长.md",
    "chars": 171,
    "preview": "#### **题目**:从 innodb 的索引结构分析,为什么索引的 key 长度不能太长?\n\n#### **出题人**:阿里巴巴出题专家:近秋/阿里云数据库产品技术部技术专家\n\n#### **参考答案**:key 太长会导致一个页当中能"
  },
  {
    "path": "01.阿里篇/1.1.7 MySQL的数据如何恢复到任意时间点?.md",
    "chars": 181,
    "preview": "#### **题目**:MySQL 的数据如何恢复到任意时间点?\n\n#### **出题人**:阿里巴巴出题专家:近秋/阿里云数据库产品技术部技术专家参考答案\n\n#### **参考答案**:恢复到任意时间点以定时的做全量备份,以及备份增量的 "
  },
  {
    "path": "01.阿里篇/1.1.8 NFS与SMB的区别?.md",
    "chars": 423,
    "preview": "#### **题目**:NFS 和 SMB 是最常见的两种 NAS(Network Attached Storage)协议,当把一个文件系统同时通过 NFS 和 SMB 协议共享给多个主机访问时,以下哪些说法是错误的:(多选)\n\nA. 不可"
  },
  {
    "path": "01.阿里篇/1.1.9 输入 ping IP 后敲回车,发包前会发生什么?.md",
    "chars": 340,
    "preview": "#### **题目**:输入 ping IP 后敲回车,发包前会发生什么?\n\n#### **出题人**:阿里巴巴出题专家:怀虎/阿里云云效平台负责人\n\n#### **参考答案**:\nping目标ip时,先查路由表,确定出接口\n- 如果落在直"
  },
  {
    "path": "01.阿里篇/1.2.0 请解释下为什么鹿晗发布恋情的时候,微博系统会崩溃,如何解决?.md",
    "chars": 199,
    "preview": "#### **题目**:请解释下为什么鹿晗发布恋情的时候,微博系统会崩溃,如何解决?\n\n#### **出题人**:阿里巴巴出题专家:江岚/阿里巴巴数据技术高级技术专家\n\n#### **参考答案**:\n\nA. 获取微博通过 pull 方式还是"
  },
  {
    "path": "01.阿里篇/1.2.1 现有一批邮件需要发送给订阅顾客,且有一个集群(集群的节点数不定,会动态扩容缩容)来负责具体的邮件发送任务,如何让系统尽快地完成发送?.md",
    "chars": 267,
    "preview": "#### **题目**:现有一批邮件需要发送给订阅顾客,且有一个集群(集群的节点数不定,会动态扩容缩容)来负责具体的邮件发送任务,如何让系统尽快地完成发送?请详述技术方案!\n\n#### **出题人**:阿里巴巴出题专家:江岚/阿里巴巴数据技"
  },
  {
    "path": "01.阿里篇/1.2.2 有一批气象观测站,现需要获取这些站点的观测数据,并存储到 Hive 中。但是气象局只提供了 api 查询,每次只能查询单个观测点。那么如果能够方便快速地获取到所有的观测点的数据?.md",
    "chars": 373,
    "preview": "#### **题目**:有一批气象观测站,现需要获取这些站点的观测数据,并存储到 Hive 中。但是气象局只提供了 api 查询,每次只能查询单个观测点。那么如果能够方便快速地获取到所有的观测点的数据?\n\n#### **出题人**:阿里巴巴"
  },
  {
    "path": "01.阿里篇/1.2.3 如何实现两金额数据相加(最多小数点两位).md",
    "chars": 461,
    "preview": "#### **题目**:如何实现两金额数据相加(最多小数点两位)?\n\n#### **出题人**:阿里巴巴出题专家:御术/蚂蚁金服数据可视化高级技术专家\n\n#### **参考答案**:\n\n其实问题并不难,就是考察候选人对 JavaScript"
  },
  {
    "path": "01.阿里篇/1.2.4 关于并行计算的一些基础开放问题.md",
    "chars": 885,
    "preview": "#### **题目**: 关于并行计算的一些基础开放问题。\n\n◼ 如何定义并计算,请分别阐述分布式内存到共享内存模式行编程的区别和实现(例子代码)?\n\n◼ 请使用 MPI 和 OpenMP 分别实现 N 个处理器对 M 个变量的求和?\n\n◼"
  },
  {
    "path": "01.阿里篇/1.2.5 请计算XILINX公司VU9P芯片的算力相当于多少TOPS,给出计算过程与公式.md",
    "chars": 197,
    "preview": "#### **题目**:请计算XILINX公司VU9P芯片的算力相当于多少TOPS,给出计算过程与公式。\n\n#### **出题人**: 阿里巴巴出题专家:隐达/阿里云异构计算资深专家\n\n#### **参考答案**:基于不同的算法,这个值在十"
  },
  {
    "path": "01.阿里篇/1.2.6 一颗现代处理器,每秒大概可以执行多少条简单的MOV指令,有哪些主要的影响因素.md",
    "chars": 268,
    "preview": "#### **题目**:一颗现代处理器,每秒大概可以执行多少条简单的MOV指令,有哪些主要的影响因素?\n\n#### **出题人**:阿里巴巴出题专家:子团/创新产品虚拟化&稳定性资深技术专家\n\n#### **参考答案**:\n\n**及格:**"
  },
  {
    "path": "01.阿里篇/1.2.7 请分析 MaxCompute 产品与分布式技术的关系、当前大数据计算平台类产品的市场现状和发展趋势.md",
    "chars": 139,
    "preview": "#### **题目**:请分析 MaxCompute 产品与分布式技术的关系、当前大数据计算平台类产品的市场现状和发展趋势。\n\n#### **出题人**:阿里巴巴出题专家:云郎/阿里 MaxCompute 高级产品专家\n\n#### **参考"
  },
  {
    "path": "01.阿里篇/1.2.8 对大数据平台中的元数据管理是怎么理解的,元数据收集管理体系是怎么样的,会对大数据应用有什么样的影响.md",
    "chars": 131,
    "preview": "#### **题目**: 对大数据平台中的元数据管理是怎么理解的,元数据收集管理体系是怎么样的,会对大数据应用有什么样的影响。\n\n#### **出题人**: 阿里巴巴出题专家:映泉/阿里巴巴高级技术专家\n\n#### **参考答案**:开放性"
  },
  {
    "path": "01.阿里篇/1.2.9 你理解常见如阿里,和友商大数据平台的技术体系差异以及发展趋势和技术瓶颈,在存储和计算两个方面进行概述.md",
    "chars": 130,
    "preview": "#### **题目**: 你理解常见如阿里,和友商大数据平台的技术体系差异以及发展趋势和技术瓶颈,在存储和计算两个方面进行概述。\n\n#### **出题人**: 阿里巴巴出题专家:映泉/阿里巴巴高级技术专家\n\n#### **参考答案**:开放"
  },
  {
    "path": "01.阿里篇/1.3.0 在云计算大数据处理场景中,每天运行着成千上万的任务,每个任务都要进行 IO 读写.md",
    "chars": 179,
    "preview": "#### **题目**:在云计算大数据处理场景中,每天运行着成千上万的任务,每个任务都要进行 IO 读写。存储系统为了更好的服务,经常会保证高优先级的任务优先执行。当多个作业或用户访问存储系统时,如何保证优先级和公平性。\n\n#### **出"
  },
  {
    "path": "01.阿里篇/1.3.1 最大频率栈.md",
    "chars": 1398,
    "preview": "#### **题目**:最大频率栈。\n实现 FreqStack,模拟类似栈的数据结构的操作的一个类。FreqStack 有两个函数:
push(int x),将整数 x 推入栈中。pop(),它移除并返回栈中出现最频繁的元素。如果最频繁的元"
  },
  {
    "path": "01.阿里篇/1.3.2 给定一个链表,删除链表的倒数第N个节点,并且返回链表的头结点.md",
    "chars": 1048,
    "preview": "#### **题目**:给定一个链表,删除链表的倒数第 N 个节点,并且返回链表的头结点。\n\n◼ 示例:\n给定一个链表: 1->2->3->4->5, 和 n = 2.\n当删除了倒数第二个节点后,链表变为 1->2->3->5.\n说明:\n给"
  },
  {
    "path": "01.阿里篇/1.3.3 如果让你设计一个通用的、支持各种数据库秒级备份和恢复的系统,你会如何设计.md",
    "chars": 116,
    "preview": "#### **题目**:如果让你设计一个通用的、支持各种数据库秒级备份和恢复的系统,你会如何设计?\n\n#### **出题人**:阿里巴巴出题专家:千震/阿里云数据库高级技术专家\n\n#### **参考答案**:开放性问题,无标准答案。"
  },
  {
    "path": "01.阿里篇/1.3.4 如果让你来设计一个支持数据库、NOSQL 和大数据之间数据实时流动的数据流及处理的系统,你会考虑哪些问题?如何设计?.md",
    "chars": 137,
    "preview": "#### **题目**:如果让你来设计一个支持数据库、NOSQL 和大数据之间数据实时流动的数据流及处理的系统,你会考虑哪些问题?如何设计?\n\n#### **出题人**:阿里巴巴出题专家:千震/阿里云数据库高级技术专家\n\n#### **参考"
  },
  {
    "path": "01.阿里篇/1.3.5 给定一个整数数组和一个整数,返回两个数组的索引,这两个索引指向的数字的加和等于指定的整数。需要最优的算法,分析算法的空间和时间复杂度.md",
    "chars": 525,
    "preview": "\n题目:给定一个整数数组和一个整数,返回两个数组的索引,这两个索引指向的数字的加和等于指定的整数。需要最优的算法,分析算法的空间和时间复杂度\n\n参考答案:\n```Java\npublic int[] twoSum(int[] nums, in"
  },
  {
    "path": "01.阿里篇/1.3.6 假如给你一个新产品,你将从哪些方面来保障它的质量?.md",
    "chars": 230,
    "preview": "#### **题目**:假如给你一个新产品,你将从哪些方面来保障它的质量?\n\n#### **出题人**:阿里巴巴出题专家:晨晖 /阿里云中间件技术部测试开发专家\n\n#### **参考答案**:\n\n可以从代码开发、测试保障、线上质量三个方面来"
  },
  {
    "path": "01.阿里篇/1.3.7 请评估一下程序的执行结果?.md",
    "chars": 613,
    "preview": "#### **题目**:请评估一下程序的执行结果?\n```\npublic class SynchronousQueueQuiz {\n    public static void main(String[] args) throws Exce"
  },
  {
    "path": "01.阿里篇/1.3.8 如何测试一辆自行车.md",
    "chars": 182,
    "preview": "#### **题目**:如何测试一辆自行车\n#### **出题人**:阿里巴巴新零售技术质量部\n\n#### **参考答案**:\n\n开放性问题,如果联系到测试角度上看的话,可以这么作答:\n\n1. 骑车试一试,看有没有问题,对应测试能不能跑通\n"
  },
  {
    "path": "01.阿里篇/1.3.9 如何判断两个链表是否相交.md",
    "chars": 144,
    "preview": "#### **题目**:如何判断两个链表是否相交\n\n#### **出题人**:阿里巴巴新零售技术质量部\n\n#### **参考答案**:\n\n$O(n^2)$: 两层遍历,总能发现是否相交\n\n$O(n)$: 一层遍历,遍历完两个链表,如果两个链"
  },
  {
    "path": "02.华为篇/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "02.华为篇/2.1.1 static有什么用途?(请至少说明两种).md",
    "chars": 219,
    "preview": "#### **题目**: static有什么用途?(请至少说明两种)\n\n#### **参考答案**: \n1) 在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。\n2) 在模块内(但在函数体外),一个被声明为静态的变量可以被"
  },
  {
    "path": "02.华为篇/2.1.2 引用与指针有什么区别?.md",
    "chars": 115,
    "preview": "#### **题目**:引用与指针有什么区别?\n\n#### **参考答案**: \n1) 引用必须被初始化,指针不必。\n2) 引用初始化以后不能被改变,指针可以改变所指的对象。\n3) 不存在指向空值的引用,但是存在指向空值的指针。\n"
  },
  {
    "path": "02.华为篇/2.1.3 描述实时系统的基本特性.md",
    "chars": 64,
    "preview": "#### **题目**:描述实时系统的基本特性\n\n#### **参考答案**: \n在特定时间内完成特定的任务,实时性与可靠性。\n"
  },
  {
    "path": "02.华为篇/2.1.4 全局变量和局部变量在内存中是否有区别?如果有,是什么区别?.md",
    "chars": 82,
    "preview": "#### **题目**:全局变量和局部变量在内存中是否有区别?如果有,是什么区别?\n\n\n#### **参考答案**: \n全局变量储存在静态数据库,局部变量在堆栈。\n"
  },
  {
    "path": "02.华为篇/2.1.5 什么是平衡二叉树?.md",
    "chars": 70,
    "preview": "#### **题目**:什么是平衡二叉树?\n\n#### **参考答案**: \n左右子树都是平衡二叉树 且左右子树的深度差值的绝对值不大于1。"
  },
  {
    "path": "02.华为篇/2.1.6 堆栈溢出一般是由什么原因导致的?.md",
    "chars": 55,
    "preview": "#### **题目**:堆栈溢出一般是由什么原因导致的?\n\n#### **参考答案**: \n没有回收垃圾资源。"
  },
  {
    "path": "02.华为篇/2.1.7 什么函数不能声明为虚函数?.md",
    "chars": 65,
    "preview": "#### **题目**:什么函数不能声明为虚函数?\n\n#### **参考答案**: \nconstructor函数不能声明为虚函数。"
  },
  {
    "path": "02.华为篇/2.1.8 冒泡排序算法的时间复杂度是什么?.md",
    "chars": 60,
    "preview": "#### **题目**: 冒泡排序算法的时间复杂度是什么?\n\n#### **参考答案**: \n时间复杂度是O(n^2)。"
  },
  {
    "path": "02.华为篇/2.1.9. Internet采用哪种网络协议?该协议的主要层次结构?.md",
    "chars": 100,
    "preview": "#### **题目**:Internet采用哪种网络协议?该协议的主要层次结构?\n\n\n#### **参考答案**: \nTcp/Ip协议\n主要层次结构为: 应用层/传输层/网络层/数据链路层/物理层。\n"
  },
  {
    "path": "02.华为篇/2.2.0 IP地址的编码分为哪俩部分?.md",
    "chars": 100,
    "preview": "#### **题目**:IP地址的编码分为哪俩部分?\n\n#### **参考答案**: \nIP地址由两部分组成,网络号和主机号。不过是要和“子网掩码”按位与上之后才能区分哪些是网络位哪些是主机位。\n\n\n"
  },
  {
    "path": "02.华为篇/2.2.1 用户输入M,N值,从1至N开始顺序循环数数,每数到M输出该数值,直至全部输出。写出C程序.md",
    "chars": 85,
    "preview": "#### **题目**:用户输入M,N值,从1至N开始顺序循环数数,每数到M输出该数值,直至全部输出。写出C程序。\n\n#### **参考答案**:\n循环链表,用取余操作做"
  },
  {
    "path": "02.华为篇/2.2.2 某32位系统下, C++程序,请计算sizeof 的值.md",
    "chars": 330,
    "preview": "#### **题目**:某32位系统下, C++程序,请计算sizeof 的值.\n\n\n#### **参考答案**:\n```\nchar str[] = “http://www.ibegroup.com/”\nchar *p = str ;\nin"
  },
  {
    "path": "03.百度篇/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "03.百度篇/3.4.8 找出数组中出现次数超过一半的数,现在有一个数组,已知一个数出现的次数超过了一半,请用O(n)的复杂度的算法找出这个数。.md",
    "chars": 500,
    "preview": "#### **题目**:找出数组中出现次数超过一半的数,现在有一个数组,已知一个数出现的次数超过了一半,请用O(n)的复杂度的算法找出这个数。\n\n#### **出题人**:阿里巴巴新零售技术质量部\n\n#### **参考答案**:\n\n遍历数组"
  },
  {
    "path": "03.百度篇/3.4.9 找出被修改过的数字.md",
    "chars": 634,
    "preview": "#### **题目**: 找出被修改过的数字\n\n#### **出题人**:阿里巴巴新零售技术质量部\n\n#### **参考答案**:\n\nemmm假设背景是寻找数组中被修改的数字或者元素,我使用代理的方式来监听数组中元素的变化,并将变化的最后数"
  },
  {
    "path": "04.腾讯篇/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "05.美团篇/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "05.美团篇/5.2.0 浅复制和深复制?怎样实现深复制?.md",
    "chars": 776,
    "preview": "#### **题目**:浅复制和深复制?怎样实现深复制?\n\n#### **出题人**:阿里巴巴新零售技术质量部\n\n#### **参考答案**:\n\n#### **参考代码**;\n```\nconst isObject = (item)=>{\n "
  },
  {
    "path": "06.头条篇/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "06.头条篇/一棵二叉树,求最大通路长度(即最大左右子树高度之和).md",
    "chars": 1569,
    "preview": "#### **题目**:一棵二叉树,求最大通路长度(即最大左右子树高度之和)  \n\n#### **参考答案**:  \n\n该题与leetcode第104题同题型,定义TreeNode结构如下:  \n\n```java\nclass TreeNod"
  },
  {
    "path": "07.滴滴篇/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "08.京东篇/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "08.京东篇/简单说一下hadoop和spark的shuffle过程.md",
    "chars": 227,
    "preview": "##### 问题:**简单说一下hadoop和spark的shuffle过程**\n\n##### 出题人:京东出题专家:阿昀/京东数据中台\n\n##### 参考答案:\n\nHadoop:map端保存分片数据,通过网络收集到reduce端。\n\nSp"
  },
  {
    "path": "09.MySQL篇/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "09.MySQL篇/9.1.0 主键 超键 候选键 外键.md",
    "chars": 1018,
    "preview": "#### **题目**:主键 超键 候选键 外键是什么\n\n#### 定义\n\n**超键(super key)**: 在关系中能唯一标识元组的属性集称为关系模式的超键\n\n**候选键(candidate key)**: 不含有多余属性的超键称为候"
  },
  {
    "path": "09.MySQL篇/9.1.1 数据库事务的四个特性及含义.md",
    "chars": 484,
    "preview": "#### **题目**:数据库事务的四个特性及含义\n\n#### **参考答案**:\n\n数据库事务transanction正确执行的四个基本要素。ACID,原子性(Atomicity)、一致性(Correspondence)、隔离性(Isol"
  },
  {
    "path": "09.MySQL篇/9.1.2 视图的作用,视图可以更改么?.md",
    "chars": 348,
    "preview": "#### **题目**:视图的作用,视图可以更改么?\n\n#### **参考答案**:\n\n视图是虚拟的表,与包含数据的表不一样,视图只包含使用时动态检索数据的查询;不包含任何列或数据。使用视图可以简化复杂的sql操作,隐藏具体的细节,保护数据"
  },
  {
    "path": "09.MySQL篇/9.1.3 drop,delete与truncate的区别.md",
    "chars": 1442,
    "preview": "#### **题目**:drop,delete与truncate的区别\n\n#### **参考答案**:\n\ndrop直接删掉表 truncate删除表中数据,再插入时自增长id又从1开始 delete删除表中数据,可以加where字句。\n\n("
  },
  {
    "path": "09.MySQL篇/9.1.4 索引的工作原理及其种类.md",
    "chars": 3052,
    "preview": "#### **题目**:索引的工作原理及其种类\n\n#### **参考答案**:\n\n**数据库索引**,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用B树及其变种B+树。\n\n在数据之外,数据库系统"
  },
  {
    "path": "09.MySQL篇/9.1.5 连接的种类.md",
    "chars": 2702,
    "preview": "#### **题目**:连接的种类\n\n#### **参考答案**:\n\n查询分析器中执行:\n```\n--建表table1,table2:\ncreate table table1(id int,name varchar(10))\ncreate "
  },
  {
    "path": "09.MySQL篇/9.1.6 数据库范式.md",
    "chars": 853,
    "preview": "#### **题目**:数据库范式\n\n#### **参考答案**:\n\n1 第一范式(1NF)\n\n在任何一个关系数据库中,第一范式(1NF)是对关系模式的基本要求,不满足第一范式(1NF)的数据库就不是关系数据库。\n所谓第一范式(1NF)是指"
  },
  {
    "path": "09.MySQL篇/9.1.7 数据库优化的思路.md",
    "chars": 1488,
    "preview": "#### **题目**:数据库优化的思路\n\n#### **参考答案**:\n\n这个我借鉴了慕课上关于数据库优化的课程。\n\n1.SQL语句优化  \n\n- 应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描"
  },
  {
    "path": "09.MySQL篇/9.1.8 存储过程与触发器的区别.md",
    "chars": 379,
    "preview": "#### **题目**:存储过程与触发器的区别\n\n#### **参考答案**:\n\n触发器与存储过程非常相似,触发器也是SQL语句集,两者唯一的区别是触发器不能用EXECUTE语句调用,而是在用户执行Transact-SQL语句时自动触发(激"
  },
  {
    "path": "09.MySQL篇/9.1.9 解释 SQL 的 left join 和 right join.md",
    "chars": 748,
    "preview": "#### **题目**:解释 SQL 的 left join 和 right join\n\n#### 出题人:阿里巴巴新零售技术质量部\n\n#### **参考答案**:\n\nleft join 和 right join 都是两个表进行 merge"
  },
  {
    "path": "10.Redis篇/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "10.Redis篇/10.1.0 使用Redis有哪些好处?.md",
    "chars": 224,
    "preview": "#### **题目**:\n\n#### **参考答案**:\n\n(1) 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)\n\n(2) 支持丰富数据类型,支持string,list,set"
  },
  {
    "path": "10.Redis篇/10.1.1 redis相比memcached有哪些优势?.md",
    "chars": 145,
    "preview": "#### **redis相比memcached有哪些优势?**:\n\n#### **参考答案**:\n\n(1) memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型\n\n(2) redis的速度比memcac"
  },
  {
    "path": "10.Redis篇/10.1.2 redis常见性能问题和解决方案.md",
    "chars": 340,
    "preview": "#### **题目**:redis常见性能问题和解决方案\n\n#### **参考答案**:\n\n(1) Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件\n\n(2) 如果数据比较重要,某个Slave开启AOF备份数据,策略设"
  },
  {
    "path": "10.Redis篇/10.1.3 MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据.md",
    "chars": 448,
    "preview": "#### **题目**:MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据\n\n#### **参考答案**:\n\n相关知识:redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。r"
  },
  {
    "path": "10.Redis篇/10.1.3 zookeeper的四种类型的znode.md",
    "chars": 317,
    "preview": "#### **题目**:zookeeper的四种类型的znode\n\n#### **参考答案**:\n\n1、PERSISTENT-持久化目录节点 \n客户端与zookeeper断开连接后,该节点依旧存在 \n2、PERSISTENT_SEQUENT"
  },
  {
    "path": "10.Redis篇/10.1.4 Memcache与Redis的区别都有哪些?.md",
    "chars": 317,
    "preview": "#### **题目**:Memcache与Redis的区别都有哪些?\n\n#### **参考答案**:\n\n1)、存储方式\n\nMemecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。\n\nRedis有部份存在硬盘上,这样能保"
  },
  {
    "path": "10.Redis篇/10.1.5 Redis 常见的性能问题都有哪些?如何解决?.md",
    "chars": 449,
    "preview": "#### **题目**:Redis 常见的性能问题都有哪些?如何解决?\n\n#### **参考答案**:\n\n1) Master写内存快照,save命令调度rdbSave函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停"
  },
  {
    "path": "10.Redis篇/10.1.6 redis最适合的场景.md",
    "chars": 376,
    "preview": "#### **题目**:redis 最适合的场景\n\n#### **参考答案**:\n\nRedis最适合所有数据in-memory的场景,虽然Redis也提供持久化功能,但实际更多的是一个disk-backed的功能,跟传统意义上的持久化有比较"
  },
  {
    "path": "10.Redis篇/10.1.7 Redis的同步机制了解么?.md",
    "chars": 175,
    "preview": "#### **题目**:Redis的同步机制了解么?\n\n#### **参考答案**:\n\n主从同步。第一次同步时,主节点做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将rdb文件全量同步到复制节点,复制节点接受完成后"
  },
  {
    "path": "10.Redis篇/10.1.8 是否使用过Redis集群,集群的原理是什么?.md",
    "chars": 163,
    "preview": "#### **题目**:是否使用过Redis集群,集群的原理是什么?\n\n#### **参考答案**:\n\nRedis Sentinel着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。 \n\nRedis Cl"
  },
  {
    "path": "11.MongoDB篇/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "11.MongoDB篇/11.1.0 什么是MongoDB?.md",
    "chars": 164,
    "preview": "#### **题目**:什么是MongoDB\n\n#### **参考答案**:\n\nMongoDB是一个文档数据库,提供好的性能,领先的非关系型数据库。采用BSON存储文档数据。2007年10月,MongoDB由10gen团队所发展。2009年"
  },
  {
    "path": "11.MongoDB篇/11.1.1 MongoDB是由哪种语言写的?.md",
    "chars": 132,
    "preview": "#### **题目**:MongoDB是由哪种语言写的\n\n#### **参考答案**:\nMongoDB用c++编写的,流行的开源数据库MySQL也是用C++开发的。C++1983年发行是一种使用广泛的计算机程序设计语言。它是一种通用程序设计"
  },
  {
    "path": "11.MongoDB篇/11.1.2 MongoDB的优势有哪些?.md",
    "chars": 201,
    "preview": "#### **题目**:MongoDB的优势有哪些\n\n#### **参考答案**:\n\n面向文档的存储:文档存储以BSON格式(有大小限制,最大16M), 内置GridFS文件系统(一般存储大于16M的文件)。\n\n * 任何属性都可以建立索引"
  },
  {
    "path": "11.MongoDB篇/11.1.3 什么是数据库?.md",
    "chars": 120,
    "preview": "#### **题目**:什么是数据库\n\n#### **参考答案**:\n\n数据库可以看成是一个电子化的文件柜,用户可以对文件中的数据运行新增、检索、更新、删除等操作。数据库是一个所有集合的容器,在文件系统中每一个数据库都有一个相关的物理文件。"
  },
  {
    "path": "11.MongoDB篇/11.1.4 什么是集合?.md",
    "chars": 136,
    "preview": "#### **题目**:什么是集合\n\n#### **参考答案**:\n\n集合就是一组 MongoDB 文档。它相当于关系型数据库(RDBMS)中的表这种概念。集合位于单独的一个数据库中。一个集合内的多个文档可以有多个不同的字段。一般来说,集合"
  },
  {
    "path": "11.MongoDB篇/11.1.5 什么是文档?.md",
    "chars": 123,
    "preview": "#### **题目**:什么是文档\n\n#### **参考答案**:\n\n文档由一组key value组成。文档是动态模式,这意味着同一集合里的文档不需要有相同的字段和结构。在关系型数据库中table中的每一条记录相当于MongoDB中的一个文"
  },
  {
    "path": "11.MongoDB篇/11.1.6 MongoDB和关系型数据库术语对比图.md",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "11.MongoDB篇/11.1.7 什么是“mongod”?.md",
    "chars": 128,
    "preview": "#### **题目**:什么是”mongod“\n\n#### **参考答案**:\n\nmongod是处理MongoDB系统的主要进程。它处理数据请求,管理数据存储,和执行后台管理操作。当我们运行mongod命令意味着正在启动MongoDB进程,"
  },
  {
    "path": "11.MongoDB篇/11.1.8 “mongod”参数有什么?.md",
    "chars": 82,
    "preview": "#### **题目**:\"mongod\"参数有什么\n\n#### **参考答案**:\n\n传递数据库存储路径,默认是\"/data/db\"\n端口号 默认是 \"27017\""
  },
  {
    "path": "11.MongoDB篇/11.1.9 什么是“mongo”?.md",
    "chars": 109,
    "preview": "#### **题目**:什么是\"mongo\"\n\n#### **参考答案**:\n\n它是一个命令行工具用于连接一个特定的mongod实例。当我们没有带参数运行mongo命令它将使用默认的端口号和localhost连接。\n\n"
  },
  {
    "path": "11.MongoDB篇/11.2.0 MongoDB哪个命令可以切换数据库?.md",
    "chars": 128,
    "preview": "#### **题目**:MongoDB哪个命令可以切换数据库\n\n#### **参考答案**:\n\nMongoDB 用use+数据库名称的方式来创建数据库。use会创建一个新的数据库,如果该数据库存在,则返回这个数据库。\n\n>use datab"
  },
  {
    "path": "11.MongoDB篇/11.2.1 什么是非关系型数据库?.md",
    "chars": 137,
    "preview": "#### **题目**:什么是非关系型数据库\n\n#### **参考答案**:\n\n非关系型数据库是对不同于传统关系型数据库的统称。非关系型数据库的显著特点是不使用SQL作为查询语言,数据存储不需要特定的表格模式。由于简单的设计和非常好的性能所"
  },
  {
    "path": "11.MongoDB篇/11.2.2 非关系型数据库有哪些类型?.md",
    "chars": 126,
    "preview": "#### **题目**:非关系型数据库有哪些类型\n\n#### **参考答案**:\n\n* Key-Value 存储 Eg:Amazon S3\n\n* 图表 Eg:Neo4J\n\n* 文档存储 Eg:MongoDB\n\n * 基于列存储 Eg:Cas"
  },
  {
    "path": "11.MongoDB篇/11.2.3 为什么用MOngoDB?.md",
    "chars": 152,
    "preview": "#### **题目**:为什么用MOngoDB?\n\n#### **参考答案**:\n\n* 架构简单\n\n* 没有复杂的连接\n\n*  深度查询能力,MongoDB支持动态查询。\n\n* 容易调试\n\n* 容易扩展\n\n* 不需要转化/映射应用对象到数据"
  },
  {
    "path": "11.MongoDB篇/11.2.4 在哪些场景使用MongoDB?.md",
    "chars": 79,
    "preview": "#### **题目**:在哪些场景使用MongoDB\n\n#### **参考答案**:\n\n* 大数据\n\n* 内容管理系统\n\n* 移动端Apps\n\n* 数据管理\n"
  },
  {
    "path": "11.MongoDB篇/11.2.5 MongoDB中的命名空间是什么意思.md",
    "chars": 614,
    "preview": "#### **题目**:MongoDB中的命名空间是什么意思?\n\n#### **参考答案**:\n\nMongoDB内部有预分配空间的机制,每个预分配的文件都用0进行填充。\n\n数据文件每新分配一次,它的大小都是上一个数据文件大小的2倍,每个数据"
  },
  {
    "path": "11.MongoDB篇/11.2.6 哪些语言支持MongoDB.md",
    "chars": 99,
    "preview": "#### **题目** : 哪些语言支持MongoDB?\n\n#### **参考答案**:\n\n* C\n\n* C++\n\n* C#\n\n* Java\n\n* Node.js\n\n* Perl\n\n* Php 等\n"
  },
  {
    "path": "11.MongoDB篇/11.2.7 在MongoDB中如何创建一个新的数据库?.md",
    "chars": 148,
    "preview": "#### **题目**:在MongoDB中如何创建一个新的数据库\n\n#### **参考答案**:\n\nMongoDB 用 use + 数据库名称 的方式来创建数据库。use 会创建一个新的数据库,如果该数据库存在,则返回这个数据库。\n\n>us"
  },
  {
    "path": "11.MongoDB篇/11.2.8 在MongoDB中如何查看数据库列表?.md",
    "chars": 73,
    "preview": "#### **题目**:在MongoDB中如何查看数据库列表\n\n#### **参考答案**:\n\n使用命令\"show dbs\"\n\n>show dbs"
  },
  {
    "path": "11.MongoDB篇/11.2.9 MongoDB中的分片是什么意思?.md",
    "chars": 246,
    "preview": "#### **题目**:MongoDB中的分片是什么意思\n\n#### **参考答案**:\n\n分片是将数据水平切分到不同的物理节点。当应用数据越来越大的时候,数据量也会越来越大。当数据量增长时,单台机器有可能无法存储数据或可接受的读取写入吞吐"
  },
  {
    "path": "11.MongoDB篇/11.3.0 如何查看使用MongoDB的连接?.md",
    "chars": 135,
    "preview": "如何查看使用MongoDB的连接Sharding - MongoDB Manual21.如何查看使用MongoDB的连接\n\n使用命令\"db.adminCommand(“connPoolStats”)\"\n\n>db.adminCommand(“"
  },
  {
    "path": "11.MongoDB篇/11.3.1 什么是复制?.md",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "11.MongoDB篇/11.3.2 在MongoDB中如何在集合中插入一个文档?.md",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "11.MongoDB篇/11.3.3 在MongoDB中如何除去一个数据库?.md",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "11.MongoDB篇/11.3.4 在MongoDB中如何创建一个集合?.md",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "11.MongoDB篇/11.3.5 在MongoDB中如何查看一个已经创建的集合?.md",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "11.MongoDB篇/11.3.6 在MongoDB中如何删除一个集合?.md",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "11.MongoDB篇/11.3.7 为什么要在MongoDB中使用分析器?.md",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "11.MongoDB篇/11.3.8 MongoDB支持主键外键关系吗?.md",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "11.MongoDB篇/11.3.9 MongoDB支持哪些数据类型?.md",
    "chars": 128,
    "preview": "+ String \n+ Integer \n+ Double \n+ Boolean\n+ Object\n+ Object ID \n+ Arrays \n+ Min/Max Keys\n+ Datetime\n+ Code\n+ Regular Expr"
  },
  {
    "path": "11.MongoDB篇/11.4.0 为什么要在MongoDB中用Code数据类型?.md",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "11.MongoDB篇/11.4.1 为什么要在MongoDB中用Regular Expression数据类型?.md",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "11.MongoDB篇/11.4.2 为什么在MongoDB中使用Object ID数据类型?.md",
    "chars": 58,
    "preview": "不使用自增是分布式维护起来非常困难。使用ObjectId可以保证不同机器都能用全局唯一的同种方法生成它并且确保不重复"
  },
  {
    "path": "11.MongoDB篇/11.4.3 如何在集合中插入一个文档?.md",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "11.MongoDB篇/11.4.4 “ObjectID”有哪些部分组成?.md",
    "chars": 37,
    "preview": "一共有四部分组成:时间戳、客户端ID、客户进程ID、三个字节的增量计数器\n"
  },
  {
    "path": "11.MongoDB篇/11.4.5 在MongoDb中什么是索引?.md",
    "chars": 122,
    "preview": "索引是为了解决数据搜索效率低下引入的一种特殊的数据结构。索引存储在一个易于遍历读取的数据集合中,索引是对数据库表中一列或多列的值进行排序的一种结构。简单的说,索引就是将`文档`按照某个(或某些)字段顺序组织起来,以便能根据该字段高效的查询。"
  },
  {
    "path": "11.MongoDB篇/11.4.6 如何添加索引?.md",
    "chars": 632,
    "preview": "MongoDB支持多种类型的索引,包括单字段索引、复合索引、多key索引、文本索引等,每种类型的索引有不同的使用场合。\n\n按照类型可分为:\n\n1. 单字段索引 \n\n```db.person.createIndex( {age: 1} ) `"
  },
  {
    "path": "11.MongoDB篇/11.4.7 MongoDB有哪些可替代产品?.md",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "11.MongoDB篇/11.4.8 如何查询集合中的文档?.md",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "11.MongoDB篇/11.4.9 用什么方法可以格式化输出结果?.md",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "11.MongoDB篇/11.5.0 如何使用AND或OR条件循环查询集合中的文档?.md",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "11.MongoDB篇/11.5.1 在MongoDB中如何更新数据?.md",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "11.MongoDB篇/11.5.2 如何删除文档?.md",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "11.MongoDB篇/11.5.3 在MongoDB中如何排序?.md",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "11.MongoDB篇/11.5.4 什么是聚合?.md",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "11.MongoDB篇/11.5.5 在MongoDB中什么是副本集?.md",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "11.MongoDB篇/11.5.6 Mongodb存储特性与内部原理.md",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "12.Zookeeper篇/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "12.Zookeeper篇/12.1.0 zookeeper是什么?.md",
    "chars": 251,
    "preview": "#### **参考答案**:\n\n> A high-performance coordination service for distributed applications\n\nZookeeper是基于Google Chubby论文的开源实现"
  },
  {
    "path": "12.Zookeeper篇/12.1.1 zookeeper提供了什么?.md",
    "chars": 59,
    "preview": "#### **题目**:zookeeper提供了什么?\n\n#### **参考答案**:\n\n1、文件系统\n2、通知机制\n"
  },
  {
    "path": "12.Zookeeper篇/12.1.2 zookeeper文件系统.md",
    "chars": 221,
    "preview": "#### **题目**:zookeeper文件系统\n\n#### **参考答案**:\n\nzookeeper提供一个类似unix文件系统目录的多层级节点命名空间(节点称为znode)。与文件系统不同的是,这些节点都可以设置关联的数据,而文件系统"
  },
  {
    "path": "12.Zookeeper篇/12.1.3 zookeeper的四种类型的znode.md",
    "chars": 238,
    "preview": "#### 题目:zookeeper的四种类型的znode\n\n#### 参考答案:\n\nPERSISTENT                持久化节点\n    \nPERSISTENT_SEQUENTIAL     顺序自动编号持久化节点,这种节"
  },
  {
    "path": "12.Zookeeper篇/12.1.4 zookeeper通知机制.md",
    "chars": 492,
    "preview": "#### **题目**:zookeeper通知机制\n\n#### **参考答案**:\n\nclient端会对某个znode建立一个watcher事件,当该znode发生变化时,zk会主动通知watch这个znode的client,然后clien"
  },
  {
    "path": "12.Zookeeper篇/12.1.5 zookeeper有哪些应用场景?.md",
    "chars": 93,
    "preview": "#### **题目**:zookeeper有哪些应用场景\n\n#### **参考答案**:\n\n1、名字服务\n\n2、配置管理\n\n3、集群管理\n\n4、分布式锁\n\n5、队列管理\n\n6、消息订阅\n"
  },
  {
    "path": "12.Zookeeper篇/12.1.6 zk的命名服务.md",
    "chars": 130,
    "preview": "#### **题目**: zk的命名服务\n\n#### **参考答案**:\n\n命名服务是指通过指定的名字来获取资源或者服务的地址,利用zk创建一个全局的路径,即是唯一的路径,这个路径就可以作为一个名字,指向集群中的集群,提供的服务的地址,或者"
  },
  {
    "path": "12.Zookeeper篇/12.1.7 zk的配置管理服务.md",
    "chars": 141,
    "preview": "#### **题目**:zk的配置管理\n\n#### **参考答案**:\n\n程序分布式的部署在不同的机器上,将程序的配置信息放在zk的znode下,当有配置发生改变时,也就是znode发生变化时,可以通过改变zk中某个目录节点的内容,利用wa"
  },
  {
    "path": "12.Zookeeper篇/12.1.8 zk的集群管理.md",
    "chars": 296,
    "preview": "#### **题目**:zookeeper集群管理\n\n#### **参考答案**:\n\n所谓集群管理无在乎两点:是否有机器退出和加入、选举master。 \n对于第一点,所有机器约定在父目录下创建临时目录节点,然后监听父目录节点的子节点变化消息"
  },
  {
    "path": "12.Zookeeper篇/12.1.9 zk的分布式锁.md",
    "chars": 916,
    "preview": "#### **题目**:zookeeper分布式锁\n\n#### **参考答案**:\n\n有了zookeeper的一致性文件系统,锁的问题变得容易。锁服务可以分为两类,一个是保持独占,另一个是控制时序。 \n对于第一类,我们将zookeeper上"
  },
  {
    "path": "12.Zookeeper篇/12.2.0 zk队列管理.md",
    "chars": 366,
    "preview": "#### **题目**:zk队列管理\n\n#### **参考答案**:\n\n两种类型的队列:\n1、同步队列,当一个队列的成员都聚齐时,这个队列才可用,否则一直等待所有成员到达。 \n2、队列按照 FIFO 方式进行入队和出队操作。 \n第一类,在约"
  },
  {
    "path": "12.Zookeeper篇/12.2.1 zk数据复制.md",
    "chars": 561,
    "preview": "#### **题目**:zk数据复制\n\n#### **参考答案**:\n\nZookeeper作为一个集群提供一致的数据服务,自然,它要在所有机器间做数据复制。\n<br/>\n数据复制的好处: \n<br/>\n1、容错:一个节点出错,不致于让整个系"
  },
  {
    "path": "12.Zookeeper篇/12.2.2 zk中zab的工作原理.md",
    "chars": 423,
    "preview": "#### **题目**:zk中zab的工作原理\n\n#### **参考答案**:\n\nZAB 是 ZooKeeper Atomic Broadcast (ZooKeeper 原子广播协议)的缩写,它是特别为 ZooKeeper 设计的崩溃可恢复"
  },
  {
    "path": "12.Zookeeper篇/12.2.3 zk是如何保证事务的顺序一致性.md",
    "chars": 285,
    "preview": "#### **题目**:zookeeper是如何保证事务的顺序一致性的?\n\n#### **参考答案**:\n\nzookeeper采用了递增的事务Id来标识,所有的proposal(提议)都在被提出的时候加上了zxid,zxid实际上是一个64"
  },
  {
    "path": "12.Zookeeper篇/12.2.4 zk集群下server工作状态.md",
    "chars": 190,
    "preview": "#### **题目**:zk集群下server工作状态\n\n#### **参考答案**:\n\n每个Server在工作过程中有四种状态: \n\nLOOKING:当前Server不知道leader是谁,正在搜寻\n\nLEADING:当前server角色"
  },
  {
    "path": "12.Zookeeper篇/12.2.5 zk是如何选举Leader的?.md",
    "chars": 1088,
    "preview": "#### **题目**:zookeeper是如何选举Leader的?\n\n#### **参考答案**:\n\n当leader崩溃或者leader失去大多数的follower,这时zk进入恢复模式,恢复模式需要重新选举出一个新的leader,让所有"
  },
  {
    "path": "12.Zookeeper篇/12.2.6 zk同步流程.md",
    "chars": 987,
    "preview": "#### **题目**:zookeeper同步流程\n\n#### **参考答案**:\n\n选完Leader以后,zk就进入状态同步过程。 \n\n1. Leader等待Follower和Observer连接;\n\n2. Follower连接leade"
  },
  {
    "path": "12.Zookeeper篇/12.2.7 分布式通知和协调.md",
    "chars": 185,
    "preview": "#### **题目**:分布式通知和协调\n\n#### **参考答案**:\n\n对于系统调度来说:操作人员发送通知实际是通过控制台改变某个节点的状态,然后zk将这些变化发送给注册了这个节点的watcher的所有客户端。\n\n对于执行情况汇报:每个"
  },
  {
    "path": "12.Zookeeper篇/12.2.8 zk的session机制.md",
    "chars": 424,
    "preview": "#### **题目**:zk的session机制\n\n#### **参考答案**:\n\nzookeeper会为每个客户端分配一个session,类似于web服务器一样,用来标识客户端的身份。\n\nsession的作用:\n\n\n- 客户端标识\n- 超"
  },
  {
    "path": "13.Nginx篇/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "14.算法篇/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "15.内存篇/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "16.CPU篇/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "17.磁盘篇/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "18.网络通信篇/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "19.安全篇/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "20.并发篇/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "2023adding.md",
    "chars": 345177,
    "preview": "\r\n# C/C++ 常见1000道面试题\r\n\r\n## 1.变量的声明和定义有什么区别\r\n\r\n为变量分配地址和存储空间的称为定义,不分配地址的称为声明。一个变量可以在多个地方声明,\r\n但是只在一个地方定义。加入 extern 修饰的是变量的声"
  },
  {
    "path": "21.面经/2020秋招面经总结.md",
    "chars": 2720,
    "preview": "\n@[LiuYongQiang6](https://github.com/LiuYongQiang6)\n\n# 目标岗位\n本硕均为机电专业相关,比赛和项目也是软硬件结合,个人软开背景中学已经学过pascal和数据结构与算法,大学本科除了C语言"
  },
  {
    "path": "README.md",
    "chars": 254490,
    "preview": "\n## 2025年最新总结,阿里,腾讯,百度,美团,头条等技术面试题目,以及答案,专家出题人分析汇总。持续更新中。\n\n* [阿里篇](#1)\n* [华为篇](#2)\n* [百度篇](#3)\n* [腾讯篇](#4)\n* [美团篇](#5)\n*"
  },
  {
    "path": "sync_link",
    "chars": 1228,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n# Author: ileler@qq.com\nimport re\nfrom shutil import move\nfrom urllib impo"
  }
]

About this extraction

This page contains the full source code of the 0voice/interview_internal_reference GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 156 files (640.2 KB), approximately 359.4k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!