GATT scan的流程
BLE scan 在bluedroid的实现中,有两个接口:一个是discovery,一个是ble observe,这两者有什么区别呢?
这里追了一下代码发现,inquiry 是上层调用search 相关的接口的实现函数,ble observe 是调用GATT scan的接口的实现函数,这篇文章分析一下,在调用GATT 接口实现scan的流程。
GATT的服务代码逻辑在哪里实现的呢?其核心代码的实现是bluedroid里面,但是,上层的应用是不可能直接调用协议栈的代码的,其实在bluedroid上面还会进行封装一个GATT的服务,其实现在package/app/Bluetooth下面的GattService.java,这里面实现了关于GATT相关的各种接口,应用层的代码通过binder 调用绑定到这些接口,并完成一系列的调用。
看一下GattService.java的实现:
/**
* Handlers for incoming service calls
*/
private static class BluetoothGattBinder extends IBluetoothGatt.Stub implements IProfileServiceBinder {
private GattService mService; public BluetoothGattBinder(GattService svc) {
mService = svc;
} public boolean cleanup() {
mService = null;
return true;
}
...
@Override
public void startScan(int appIf, boolean isServer, ScanSettings settings,
List<ScanFilter> filters, List storages, String callingPackage) {
GattService service = getService();
if (service == null) return;
service.startScan(appIf, isServer, settings, filters, storages, callingPackage);
} public void stopScan(int appIf, boolean isServer) {
GattService service = getService();
if (service == null) return;
service.stopScan(new ScanClient(appIf, isServer));
} ...
}
可以看到其代码中实现了一个BluetoothGattBinder,这个上层应用程序在绑定完成的时候,会得到这个binder接口。我们也可以看到,这个binder实现的也是GATT相关的基本的接口。
分析一下startScan接口,发现其是调用到另一个service 的startScan的接口,那这个service 是哪里来的呢?其实这个service就是GATTService本身,在initBinder的时候,将this 指针传入。
    protected IProfileServiceBinder initBinder() {
        return new BluetoothGattBinder(this);
    }
我们的重点是分析startScan 这个接口的流程,现在我们看GATTService是如何实现这个接口的
void startScan(int appIf, boolean isServer, ScanSettings settings,
List<ScanFilter> filters, List<List<ResultStorageDescriptor>> storages,
String callingPackage) {
...
final ScanClient scanClient = new ScanClient(appIf, isServer, settings, filters, storages);
...
mScanManager.startScan(scanClient);
}
新建了一个scanClient 类,并将此类传入到mScanManager.startScan中:
    void startScan(ScanClient client) {
        sendMessage(MSG_START_BLE_SCAN, client);
    }
此时的代码走到了ScanManager.java里面,不管上层的代码如何流转,我们知道,最后肯定还是调用到JNI 的接口,然后到达bluedroid里面,接着看:
// Handler class that handles BLE scan operations.
private class ClientHandler extends Handler {
...
@Override
public void handleMessage(Message msg) {
ScanClient client = (ScanClient) msg.obj;
switch (msg.what) {
case MSG_START_BLE_SCAN://处理事件
handleStartScan(client);
break;
case MSG_STOP_BLE_SCAN:
handleStopScan(client);
break;
...
}
} void handleStartScan(ScanClient client) {//处理scan的实现函数
Utils.enforceAdminPermission(mService);...
// Begin scan operations.
if (isBatchClient(client)) {
mBatchClients.add(client);
mScanNative.startBatchScan(client);
} else {
mRegularScanClients.add(client);
mScanNative.startRegularScan(client);
if (!mScanNative.isOpportunisticScanClient(client)) {
mScanNative.configureRegularScanParams();
}
}
} ...
}
这边分析一下startBatchScan是vendor command 相关,那么一般都是调用到mScanNative.startRegularScan,这边已经调用到了native层面,具体看看其实现:
        void startRegularScan(ScanClient client) {
            if (isFilteringSupported() && mFilterIndexStack.isEmpty()
                    && mClientFilterIndexMap.isEmpty()) {
                initFilterIndexStack();
            }
            if (isFilteringSupported()) {
                configureScanFilters(client);
            }
            // Start scan native only for the first client.
            if (numRegularScanClients() == 1) {
                gattClientScanNative(true);
            }
        }
这边继续往下调用到gattClientScanNative(true) :这里调用到JNI 层,其实现在文件com_android_bluetooth_gatt.cpp
static void gattClientScanNative(JNIEnv* env, jobject object, jboolean start)
{
if (!sGattIf) return;
sGattIf->client->scan(start);
}
到这里就很明确了,其最终调用的是sGattIf中client 的scan的接口,那其接口是怎么样的呢?
其是在bluetooth.c里面通过get_profile_interface 来获取GATT的interface的,
static const btgatt_interface_t btgattInterface = {
    sizeof(btgattInterface),
    btif_gatt_init,
    btif_gatt_cleanup,
    &btgattClientInterface,
    &btgattServerInterface,
};
中的client 的接口如下:
const btgatt_client_interface_t btgattClientInterface = {
    btif_gattc_register_app,
    btif_gattc_unregister_app,
    btif_gattc_scan,
    btif_gattc_open,
    btif_gattc_close,
    btif_gattc_listen,
...
}
那其实调用的就是:btif_gattc_scan
static bt_status_t btif_gattc_scan( bool start )
{
CHECK_BTGATT_INIT();
btif_gattc_cb_t btif_cb;
return btif_transfer_context(btgattc_handle_event, start ? BTIF_GATTC_SCAN_START : BTIF_GATTC_SCAN_STOP,
(char*) &btif_cb, sizeof(btif_gattc_cb_t), NULL);
}
这里将处理的流程transfer到bt_jni_workqueue_thread线程了,从这个线程的名字来看,主要是处理从JNI 下来的事件。看看具体做了什么:
static void btgattc_handle_event(uint16_t event, char* p_param)
{
...
btif_gattc_cb_t* p_cb = (btif_gattc_cb_t*) p_param;
if (!p_cb) return;
switch (event)
{
...
case BTIF_GATTC_SCAN_START:
btif_gattc_init_dev_cb();
BTA_DmBleObserve(TRUE, , bta_scan_results_cb);//调用的是这个函数
break;
...
继续看BTA_DmBleObserve,注意第一个参数是true,表示开始scan,第二个参数是持续时间,0表示一直持续:
extern void BTA_DmBleObserve(BOOLEAN start, UINT8 duration,
tBTA_DM_SEARCH_CBACK *p_results_cb)
{
tBTA_DM_API_BLE_OBSERVE *p_msg;
APPL_TRACE_API("BTA_DmBleObserve:start = %d ", start);
if ((p_msg = (tBTA_DM_API_BLE_OBSERVE *) GKI_getbuf(sizeof(tBTA_DM_API_BLE_OBSERVE))) != NULL)
{
memset(p_msg, , sizeof(tBTA_DM_API_BLE_OBSERVE));
p_msg->hdr.event = BTA_DM_API_BLE_OBSERVE_EVT;//向bt_workqueue_thread发送BTA_DM_API_BLE_OBSERVE_EVT
p_msg->start = start;
p_msg->duration = duration;
p_msg->p_cback = p_results_cb;
bta_sys_sendmsg(p_msg);
}
}
看了一下代码发现bt_workqueue_thread 是处理事件的主线程,bta_sys_sendmsg(p_msg); 这个函数是将消息发送到btu_bta_msg_queue,而这个queue是和bt_workqueue_thread绑定的,队列里面的消息都会在这个线程里面处理:
void bta_sys_sendmsg(void *p_msg)
{
if (btu_bta_msg_queue)
fixed_queue_enqueue(btu_bta_msg_queue, p_msg);
}
那现在 关于scan的event 的处理已经来到了另一个线程:bt_workqueue_thread,那么该队列里面有了数据线程如何处理?
fixed_queue_register_dequeue(btu_bta_msg_queue,
thread_get_reactor(bt_workqueue_thread),
btu_bta_msg_ready,
NULL);
根据上面的代码,我们知道会调用到btu_bta_msg_ready:
void btu_bta_msg_ready(fixed_queue_t *queue, UNUSED_ATTR void *context) {
    BT_HDR *p_msg = (BT_HDR *)fixed_queue_dequeue(queue);//消息出列
    bta_sys_event(p_msg);
}
也就是先让消息处理,然后再调用bta_sys_event来处理:那至此我们知道,凡是调用到bta_sys_sendmsg,那最终处理的函数都是bta_sys_event来处理,而这个函数的处理方式也是一种dispatch的机制:
void bta_sys_event(BT_HDR *p_msg)
{
...
/* get subsystem id from event */
id = (UINT8) (p_msg->event >> );
/* verify id and call subsystem event handler */
if ((id < BTA_ID_MAX) && (bta_sys_cb.reg[id] != NULL))
{
freebuf = (*bta_sys_cb.reg[id]->evt_hdlr)(p_msg);
}
...
}
其思想就是找到该事件对应的处理函数,这些event的高8bit 是属于事件的类型,或者称为主事件,而event的低8 bit是事件的子类,或者称为子事件。处理的过程是先通过主事件找到事件的处理函数handler(当然肯定是事先注册好的),然后在该处理函数中处理子事件。
那该事件的处理函数handler 是什么呢?
/*******************************************************************************
**
** Function bta_sys_register
**
** Description Called by other BTA subsystems to register their event
** handler.
**
**
** Returns void
**
*******************************************************************************/
void bta_sys_register(UINT8 id, const tBTA_SYS_REG *p_reg)
{
bta_sys_cb.reg[id] = (tBTA_SYS_REG *) p_reg;
bta_sys_cb.is_reg[id] = TRUE;
}
这里是注册的地方,根据函数的注释,是BTA 的子系统注册自己的event 的处理函数 时候所调用的。下图很容易看出有哪些模块调用这个注册函数

对于BTA_DM_API_BLE_OBSERVE_EVT 这个event 可知其主事件是BTA_ID_DM = 1 ,其注册 的地方在BTA_EnableBluetooth:
bta_sys_register (BTA_ID_DM, &bta_dm_reg );
那现在我们知道,其处理的函数的入口就是bta_dm_reg:
static const tBTA_SYS_REG bta_dm_reg =
{
bta_dm_sm_execute,
bta_dm_sm_disable
};
BOOLEAN bta_dm_sm_execute(BT_HDR *p_msg)
{
UINT16 event = p_msg->event & 0x00ff;//取出子事件
/* execute action functions */
if(event < BTA_DM_NUM_ACTIONS)
{
(*bta_dm_action[event])( (tBTA_DM_MSG*) p_msg);
}
return TRUE;
}
这里我们发现,其设计还是比较巧妙,每个event 对应的处理函数,是在一个大的数组中,用事件的子事件(低8bit)来寻址,这有点分页的意味了。
这里该事件真正的处理函数是bta_dm_ble_observe:并调用如下代码:
((status = BTM_BleObserve(TRUE, p_data->ble_observe.duration,
bta_dm_observe_results_cb, bta_dm_observe_cmpl_cb))!= BTM_CMD_STARTED)
这里我们发现,函数调用已经进入到stack里面了,BTM_BleObserve,看看其具体实现,这里我们应该还记得,这里的第二个参数传进来的时候是0:
tBTM_STATUS BTM_BleObserve(BOOLEAN start, UINT8 duration,
tBTM_INQ_RESULTS_CB *p_results_cb, tBTM_CMPL_CB *p_cmpl_cb)
{
...
UINT32 scan_interval = !p_inq->scan_interval ? BTM_BLE_GAP_DISC_SCAN_INT : p_inq->scan_interval;//发现参数是优先使用inquiry的参数
UINT32 scan_window = !p_inq->scan_window ? BTM_BLE_GAP_DISC_SCAN_WIN : p_inq->scan_window;
...
if (start)
{
/* shared inquiry database, do not allow observe if any inquiry is active */
if (BTM_BLE_IS_OBS_ACTIVE(btm_cb.ble_ctr_cb.scan_activity))//如果有observe 直接返回
{
BTM_TRACE_ERROR("%s Observe Already Active", __func__);
return status;
}
btm_cb.ble_ctr_cb.p_obs_results_cb = p_results_cb;
btm_cb.ble_ctr_cb.p_obs_cmpl_cb = p_cmpl_cb;
status = BTM_CMD_STARTED;
/* scan is not started */
if (!BTM_BLE_IS_SCAN_ACTIVE(btm_cb.ble_ctr_cb.scan_activity))//没有其他的scan 行为才继续执行
{
/* allow config of scan type */
p_inq->scan_type = (p_inq->scan_type == BTM_BLE_SCAN_MODE_NONE) ?
BTM_BLE_SCAN_MODE_ACTI: p_inq->scan_type;
...
p_inq->scan_duplicate_filter = BTM_BLE_DUPLICATE_DISABLE;
status = btm_ble_start_scan();//开始scan
}
if (status == BTM_CMD_STARTED)
{
btm_cb.ble_ctr_cb.scan_activity |= BTM_LE_OBSERVE_ACTIVE;
if (duration != )
/* start observer timer */
btu_start_timer (&btm_cb.ble_ctr_cb.obs_timer_ent, BTU_TTYPE_BLE_OBSERVE, duration);//这里注意,如果duration设置了,那么经过一定时间就会超时,然后会停止scan,如果没有设置这个值,就会一直scan
}
}
这里注意一下代码中有这样一句注释:shared inquiry database, do not allow observe if any inquiry is active,说明oberve的优先级还是很低的。从代码中也 可以看出只有当没有其他的scan的行为,observe才会继续进行。另外对于scan type 是active还是passive的问题,当p_inq->scan_interval 没有设置的话,就使用active,否则就使用当前的设置值。从这也可以看出,active 是优先被使用的。
最后看看btm_ble_start_scan的实现,这个就很简单了,直接通过HCI 来发送命令了:
tBTM_STATUS btm_ble_start_scan(void)
{
tBTM_BLE_INQ_CB *p_inq = &btm_cb.ble_ctr_cb.inq_var;
tBTM_STATUS status = BTM_CMD_STARTED; /* start scan, disable duplicate filtering */
if (!btsnd_hcic_ble_set_scan_enable (BTM_BLE_SCAN_ENABLE, p_inq->scan_duplicate_filter))//HCI command
{
status = BTM_NO_RESOURCES;
}
else
{
if (p_inq->scan_type == BTM_BLE_SCAN_MODE_ACTI)
btm_ble_set_topology_mask(BTM_BLE_STATE_ACTIVE_SCAN_BIT);//更新拓扑
else
btm_ble_set_topology_mask(BTM_BLE_STATE_PASSIVE_SCAN_BIT);
}
return status;
}
scan结果的回报:
前面注册的时候,我们看到
case BTIF_GATTC_SCAN_START:
btif_gattc_init_dev_cb();
BTA_DmBleObserve(TRUE, , bta_scan_results_cb);
break;
其回调函数是bta_scan_results_cb,当搜索结果上来的时候,该函数会被调用:
static void bta_scan_results_cb (tBTA_DM_SEARCH_EVT event, tBTA_DM_SEARCH *p_data)
{
...
switch (event)
{
case BTA_DM_INQ_RES_EVT:
{
...
}
break; case BTA_DM_INQ_CMPL_EVT:
{
...
}
btif_transfer_context(btif_gattc_upstreams_evt, BTIF_GATT_OBSERVE_EVT,
(char*) &btif_cb, sizeof(btif_gattc_cb_t), NULL);
}
代码中针对BTA_DM_INQ_RES_EVT和BTA_DM_INQ_CMPL_EVT 都会有自己的一些处理,但是最后都要经过btif_gattc_upstreams_evt的处理,并且是event = BTIF_GATT_OBSERVE_EVT
看具体的代码实现:
case BTIF_GATT_OBSERVE_EVT:
{
btif_gattc_cb_t *p_btif_cb = (btif_gattc_cb_t*) p_param;
...
BTIF_STORAGE_FILL_PROPERTY(&properties,
BT_PROPERTY_TYPE_OF_DEVICE, sizeof(dev_type), &dev_type);
btif_storage_set_remote_device_property(&(p_btif_cb->bd_addr), &properties); HAL_CBACK(bt_gatt_callbacks, client->scan_result_cb,
&p_btif_cb->bd_addr, p_btif_cb->rssi, p_btif_cb->value);
break;
}
上面做的主要就是保存设备的属性,以及向上层汇报相关的设备信息:使用bt_gatt_callbacks中的 client->scan_result_cb,接口。
那这个接口是哪里来的呢?
static bt_status_t btif_gatt_init( const btgatt_callbacks_t* callbacks )
{
bt_gatt_callbacks = callbacks;
return BT_STATUS_SUCCESS;
}
发现是gatt 模块init的时候赋值的,那么我们就知道其callback 来源于JNI层面:
static const btgatt_callbacks_t sGattCallbacks = {
    sizeof(btgatt_callbacks_t),
    &sGattClientCallbacks,
    &sGattServerCallbacks
};
static const btgatt_client_callbacks_t sGattClientCallbacks = {
    btgattc_register_app_cb,
    btgattc_scan_result_cb,//此函数
    btgattc_open_cb,
...
通过JNI方法的回调:
void btgattc_scan_result_cb(bt_bdaddr_t* bda, int rssi, uint8_t* adv_data)
{
...
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onScanResult
, address, rssi, jb);//调用method_onScanResult
... checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__); }
这个方法的实现是在java层,那到底对应于哪一个文件呢?
int register_com_android_bluetooth_gatt(JNIEnv* env)
{
int register_success =
jniRegisterNativeMethods(env, "com/android/bluetooth/gatt/ScanManager$ScanNative",
sScanMethods, NELEM(sScanMethods));
register_success &=
jniRegisterNativeMethods(env, "com/android/bluetooth/gatt/AdvertiseManager$AdvertiseNative",
sAdvertiseMethods, NELEM(sAdvertiseMethods));
return register_success &
jniRegisterNativeMethods(env, "com/android/bluetooth/gatt/GattService",
sMethods, NELEM(sMethods));
}
发现sMethods对应于"com/android/bluetooth/gatt/GattService" ,那我们知道其实现是在GattService.java里面。看具体的实现:
    void onScanResult(String address, int rssi, byte[] adv_data) {
        if (VDBG) Log.d(TAG, "onScanResult() - address=" + address
                    + ", rssi=" + rssi);
        List<UUID> remoteUuids = parseUuids(adv_data);
        for (ScanClient client : mScanManager.getRegularScanQueue()) {
            if (client.uuids.length > ) {
                int matches = ;
                for (UUID search : client.uuids) {
                    for (UUID remote: remoteUuids) {
                        if (remote.equals(search)) {
                            ++matches;
                            break; // Only count 1st match in case of duplicates
                        }
                    }
                }
                if (matches < client.uuids.length) continue;
            }
            if (!client.isServer) {
                ClientMap.App app = mClientMap.getById(client.clientIf);
                if (app != null) {
                    BluetoothDevice device = BluetoothAdapter.getDefaultAdapter()
                            .getRemoteDevice(address);
                    ScanResult result = new ScanResult(device, ScanRecord.parseFromBytes(adv_data),
                            rssi, SystemClock.elapsedRealtimeNanos());
                    // Do no report if location mode is OFF or the client has no location permission
                    // PEERS_MAC_ADDRESS permission holders always get results
                    if (hasScanResultPermission(client) && matchesFilters(client, result)) {
                        try {
                            ScanSettings settings = client.settings;
                            if ((settings.getCallbackType() &
                                    ScanSettings.CALLBACK_TYPE_ALL_MATCHES) != ) {
                                app.callback.onScanResult(result);
                            }
                        } catch (RemoteException e) {
                            Log.e(TAG, "Exception: " + e);
                            mClientMap.remove(client.clientIf);
                            mScanManager.stopScan(client);
                        }
                    }
                }
            } else {
                ServerMap.App app = mServerMap.getById(client.clientIf);
                if (app != null) {
                    try {
                        app.callback.onScanResult(address, rssi, adv_data);
                    } catch (RemoteException e) {
                        Log.e(TAG, "Exception: " + e);
                        mServerMap.remove(client.clientIf);
                        mScanManager.stopScan(client);
                    }
                }
            }
        }
    }
到这里呢,协议栈就将关于设备的信息上传到bluetooth.apk了,在这个函数里面,我们可以看到其最终调用到app.callback.onScanResult(address, rssi, adv_data);,这边应该是回调到更上一层应用。
GATT scan的流程的更多相关文章
- GATT服务搜索流程(一)
		
GATT的规范阅读起来还是比较简答, 但是这样的规范在代码上是如何实现的呢?下面就分析一下bluedroid 协议栈关于GATT的代码流程. BLE的设备都是在SMP之后进行ATT的流程的交互.从代码 ...
 - GATT服务搜索流程(二)
		
关于bta_dm_cb.p_sec_cback,这里我们之前已经分析过,他就是bte_dm_evt ,最终调用的函数btif_dm_upstreams_evt : static void btif_d ...
 - HBase Scan流程分析
		
HBase Scan流程分析 HBase的读流程目前看来比较复杂,主要由于: HBase的表数据分为多个层次,HRegion->HStore->[HFile,HFile,...,MemSt ...
 - Android WIFI 启动流程
		
参考:http://blog.chinaunix.net/uid-26215986-id-3260413.html 一. WIFI 工作步骤 1. Wifi模块初始化 2. Wifi启动 3. 查找热 ...
 - HQueue:基于HBase的消息队列
		
HQueue:基于HBase的消息队列 凌柏 1. HQueue简介 HQueue是一淘搜索网页抓取离线系统团队基于HBase开发的一套分布式.持久化消息队列.它利用HTable存储消息数据 ...
 - 【转载】HBase 数据库检索性能优化策略
		
转自:http://www.ibm.com/developerworks/cn/java/j-lo-HBase/index.html 高性能 HBase 数据库 本文首先介绍了 HBase 数据库基本 ...
 - [Android] 输入系统(三):加载按键映射
		
映射表基本概念 由于Android调用getEvents得到的key是linux发送过来的scan code,而Android处理的是类似于KEY_UP这种统一类型的key code,因此需要有映射表 ...
 - HBase 数据库检索性能优化策略--转
		
https://www.ibm.com/developerworks/cn/java/j-lo-HBase/index.html HBase 数据表介绍 HBase 数据库是一个基于分布式的.面向列的 ...
 - HBase多租户机制分析
		
在HBase1.1.0发布之前,HBase同一集群上的用户.表都是平等的,没有优劣之分.这种’大同’社会看起来完美,实际上有很多问题.最棘手的主要有这么两个,其一是某些业务较其他业务重要,需要在资源有 ...
 
随机推荐
- FTP上传下载类
			
public class FtpOperation { public static void UploadFile(FileInfo fileinfo, string targetDir, strin ...
 - 使用托管快照创建作为 Azure 托管磁盘存储的 VHD 的副本
			
创建快照 创建 OS 或数据磁盘 VHD 的快照,以便将其用作备份或用于排查 VM 问题. 快照是 VHD 的完整只读副本. 使用 Azure 门户创建快照 登录到 Azure 门户. 首先在左上角单 ...
 - Oracle EBS OM 删除订单行
			
DECLARE l_header_rec OE_ORDER_PUB.Header_Rec_Type; l_line_tbl OE_ORDER_PUB.Line_Tbl_Type; l_action_r ...
 - Oracle EBS AP取消核销
			
--取消核销 created by jenrry 20170425 DECLARE l_result BOOLEAN; l_msg_count NUMBER; l_result_n varchar2( ...
 - IntelliJ IDEA2018激活方法
			
前言: IntelliJ IDEA2018请在官网下载:https://www.jetbrains.com/idea/ 一.license server激活 输入http://idea.jialeen ...
 - 一个服务器多个tomcat的配置
			
下面我们把配置的详细过程写在下面,以供参考:(此例以配置三个Tomcat为例)1. 下载apache-tomcat-7.0.63,下载下来的文件为apache-tomcat-7.0.63.zip.2. ...
 - SSH批量分发管理
			
ssh服务认证类型主要有两个: 基于口令的安全验证: 基于口令的安全验证的方式就是大家一直在用的,只要知道服务器的ssh连接账户.口令.IP及开发的端口,默认22,就可以通过ssh客户端登陆到这台远程 ...
 - python设计模式之工厂模式
			
一.理解工厂模式 在面向对象编程中,术语“工厂”表示一个负责创建替他类型对象的类.通常情况下,作为一个工厂的类有一个对象以及与它关联的多个方法.客户端使用某些参数调用此方法,之后,工厂会据此创建所需类 ...
 - Windows窗体数据抓取详解
			
最近在客户项目上刚好遇到一个问题,项目需求是要获取某台机床的实时状态,问题点刚好就在于该机床不是传统意义上的数控机床,也不是PLC控制器,只有一个上传下载程序文件的应用程序,上面刚好有几个按钮可以大概 ...
 - NOIP2018考前抱佛脚——数据结构基础及STL实现
			
目录 动态数组 栈 队列 优先队列 动态数组 srand(time(0)); std::vector<int> qwq; for(int i = 1;i <= 10;++i) qwq ...