目錄
python3.6中下列程序的輸出是什麼,具體的題目記不清楚了,在這裏補充一下python2 和python3在輸出上的區別
C語言中的malloc/free與C++中的new/delete的區別
sizeof/strlen?在對字符串數組求sizeof的時候算不算最後的\0?
用預處理指令#define 聲明一個常數,用以表明1年中有多少秒(忽略閏年問題)
寫一個"標準"宏MIN ,這個宏輸入兩個參數並返回較小的一個。
嵌入式系統總是要用戶對變量或寄存器進行位操作。給定一個整型變量a,寫兩段代碼,第一個設置a的bit 3,第二個清除a 的bit 3。在以上兩個操作中,要保持其它位不變。
進程通信有哪些,如果我需要傳輸大量數據使用那種方式效率最高。
項目介紹需要複習的點
任務:
1、瞭解一下安卓系統的input子系統, led子系統
2、自己做的基於安卓開發的應用(智能家居)流程,需要更細緻的瞭解
3、圖像方面jni調用在熟悉一下
4、飛控PID算法需要了解一下
4、最後就是串口通信這塊I^c/uart需要在鞏固一下
1、“挑戰杯”科技作品競賽-智能窗戶控制系統
簡介:通過傳感器獲取室內外環境情況,控制智能窗戶的狀態,遠程視頻監控、
定時開關窗戶,實時記錄用戶對窗的使用習慣,智能調節窗戶開閉。
負責在 RT5350 平臺,運行 openwrt 系統,接收來自 zigbee 網關獲取溫溼度 /CO2 傳感器/人體感應信息,使用 mjpg_streamer 開源項目將攝像頭採集到的 視頻數據通過網絡傳輸到客戶端,實現視頻監控。(原理:從攝像頭採集圖 像,並把他們已流的形式,通過基於 IP 的網絡傳輸到安卓端)。在 Android stdio 平臺開發安卓應用,接收遠程服務器的數據,實現遠程監控家庭情況;
問題:mjpg_streamer項目介紹/數據怎麼傳輸到服務器/
概念:在Linux上運行的視頻服務器,可以將攝像頭採集到的視頻數據通過網絡傳輸到客戶端,實現視頻監控
參考:https://blog.csdn.net/yi412/article/details/37649641
輸出插件的實現是一個http服務器
主要結構:mjpg_streamer主要由三部分構成,主函數mjpg_streamer.c和輸入、輸出組件,其中輸入、輸出組件通常是input_uvc.so和output_http.so,他們是以動態鏈接庫的形式在主函數中調用的。
主函數的主要功能有:
1.解析命令行的各個輸入參數。2.判斷程序是否需要成爲守護進程,如果需要,則執行相應的操作。3.初始化global全局變量。4.打開並配置輸入、輸出插件。5.運行輸入、輸出插件。
輸入插件將採集到的視頻送到全局緩存中,輸出插件則從全局緩存中讀取數據併發送。輸出插件的實現是一個http服務器,這裏就不介紹了。輸入插件的視頻採集功能主要是通過Linux的V4L2接口( https://blog.csdn.net/Jfuck/article/details/8169352)實現的,主要是4個函數input_init()、 input_stop()、 input_run()和 input_cmd()。其中iniput_run()函數創建了線程cma_thread(),這個線程很重要,該函數的作用是抓取一幀的圖像,並複製到全局緩衝區。
zigbee
ZigBee技術是一種近距離、低複雜度、低功耗、低速率、低成本的雙向無線通訊技術。
主要用於距離短、功耗低且傳輸速率不高的各種電子設備之間進行數據傳輸以及典型的有週期性數據、間歇性數據和低反應時間數據傳輸的應用。
zigbee網絡提供3種網絡設備類型,分別是協調器(Coordinator)、路由器(Router)和終端節點(EndDevice)。協調器是一個全功能設備,在整個網絡中的權限最高,功能最強大。它是一個網絡的建立者,維護整個zigbee網絡。在協議棧中,每個源文件都可以定義多個功能設備的實現函數。它們都通過編譯來實現各個設備的需要
- 基本概念
ZigBee技術是一種近距離、低複雜度、低功耗、低速率、低成本的雙向無線通訊技術。
主要用於距離短、功耗低且傳輸速率不高的各種電子設備之間進行數據傳輸以及典型的有週期性數據、間歇性數據和低反應時間數據傳輸的應用。
zigbee網絡提供3種網絡設備類型,分別是協調器(Coordinator)、路由器(Router)和終端節點(EndDevice)。協調器是一個全功能設備,在整個網絡中的權限最高,功能最強大。它是一個網絡的建立者,維護整個zigbee網絡。在協議棧中,每個源文件都可以定義多個功能設備的實現函數。它們都通過編譯來實現各個設備的需要
- Zstack系統結構
- 程序運行框圖
安卓端開發
- WIFI通信( FlashActivity.java)
(1)使用WIFI傳輸數據之前,首先要到AndroidManifest.xml文件裏面聲明wifi權限:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
(2)獲得wifi id和port
ipEdt = (AutoCompleteTextView) findViewById(R.id.ip);
portEdt = (EditText) findViewById(R.id.port);
wifi = (WifiManager) getSystemService(Context.WIFI_SERVICE);//獲得系統wifi服務
(3)通過使用SharedPreferences存儲錄入的ip地址以及端口號
sp = getSharedPreferences("config", MODE_PRIVATE);
/* 創建好配置文件後,以後就可以用它的edit來操作配置文件了 */
editor = sp.edit();
String names[] = sp.getString("ip", "").split(":");//獲得上次輸入的ip地址
//將數組或List集合的多個值包裝成多個列表項,使用的android自帶的//android.R.layout.simple_list_item_1
ArrayAdapter<String> adapter = new ArrayAdapter<String>(mContext,
android.R.layout.simple_dropdown_item_1line, names);
ipEdt.setAdapter(adapter);
2、圖像jni調用流程
使用 NDK,通過 JNI 的方式來調用 C++ 的方法。
流程:
Gradle 調用您的外部構建腳本 CMakeLists.txt。
CMake 按照構建腳本中的命令將 C++ 源文件 native-lib.cpp 編譯到共享的對象庫中,並命名爲 libnative-lib.so,Gradle 隨後會將其打包到 APK 中。
運行時,應用的 MainActivity 會使用 System.loadLibrary() 加載原生庫。現在,應用可以使用庫的原生函數 stringFromJNI()。
MainActivity.onCreate() 調用 stringFromJNI(),這將返回“Hello from C++”並使用這些文字更新 TextView。
要手動配置 Gradle 以關聯到您的原生庫,需要將 externalNativeBuild {} 塊添加到模塊級 build.gradle 文件中,並使用 cmake {} 或 ndkBuild {} 對其進行配置。
概念:
JNI:JNI是一套編程接口,用來實現Java代碼與本地的C/C++代碼進行交互;
NDK: NDK是Google開發的一套開發和編譯工具集,可以生成動態鏈接庫,主要用於Android的JNI開發
首先是將寫好的C/C++代碼編譯成對應平臺的動態庫(windows一般是dll文件,linux一般是so文件等),
流程:
1、使用C++11擴展:採用cmake編譯.so文件的方法,所以相當於用java接口去調用c++,使用NDK技術, 多個cpp文件,下面也多了一個CMakeLists.txt
2、MobileNetssd.cpp:加載轉成ncnn的MobileNetSSD_deploy.id.h等模型參數/把像素轉換成data,並指定通道順序/根據提供的*id.h文件加載ncnn網絡,輸出結果xywh/
3、CMakeLists.txt修改:添加ncnn for android 的包/在add library中 MobileNetssd爲生成.so的文字最好直接和.cpp名字一樣,需要更改/在target_link_librarie以下三個都要添加MobileNetssd 、ncnn_lib這個ncnn的lib的add、jnigraphics#這個jni也需要add/
4、build.gradle修改:cmake添加C++11多線程和手機硬件架構armeabi-v7a/需要添加 把 .a文件導入"src/main/jniLibs"src/cpp
5、編寫java接口:命名爲和.cpp文件一樣的名稱這裏就是MobileNetssd.java
package com.example.che.mobilenetssd_demo;
import android.graphics.Bitmap;
/**
* MobileNetssd的java接口,與本地c++代碼相呼應 native爲本地 此文件與 MobileNetssd.cpp相呼應
*/
public class MobileNetssd {
public native boolean Init(byte[] param, byte[] bin); // 初始化函數
public native float[] Detect(Bitmap bitmap); // 檢測函數
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("MobileNetssd");//最後
}
}
- public native boolean Init(byte[] param, byte[] bin); // 初始化函數 對應於NDK編寫的.cpp文件中的JNIEXPORT jboolean JNICALL
- Java_com_example_che_mobilenetssd_demo_MobileNetssd_Init(JNIEnv *env, jobject obj, jbyteArray param, jbyteArray bin)
- public native float[] Detect(Bitmap bitmap); // 檢測函數 對應於NDK編寫的.cpp文件中的JNIEXPORT jfloatArray JNICALL Java_com_example_che_mobilenetssd_demo_MobileNetssd_Detect(JNIEnv* env, jobject thiz, jobject bitmap)
其實有規律可尋以第二個爲例子
函數前三個都是JNIEXPORT +函數返回類型(NDK形式)+JNICALL
Java_com_example_che_mobilenetssd_demo_MobileNetssd_Detect(JNIEnv* env, jobject thiz, jobject bitmap)
對於這個命名先看下面這個圖你可能就懂了
相當於絕對路徑那種感覺了顯示java文件夾,再是com.example.che.mobilenetssd_demo這個包再是MobileNetssd.java文件最後是 java接口文件中的public native float[] Detect(Bitmap bitmap); 函數的文件名,即Java_com_example_che_mobilenetssd_demo_MobileNetssd_Detect,後面參數則爲
(JNIEnv* env, jobject thiz,+原函數的函數參數(NDK類型格式))
其實正常來講NDK這種不應該是先寫cpp再寫java接口而是應該先寫java接口再利用IDE本身帶有的NDK的開發環境可以直接雙點寫的java接口的函數(函數格式如上需要加一個native)之後快捷鍵按ALT+ENTER即可直接在cpp文件中添加成功,具體請看NDK技術即可,這裏不贅餘了。
接下來就是我的其他.java文件以及UI的XML文件了
6、MainActivity.java:java接口實例化,利用java函數調用NDK c++函數/MobileNetssd初始化,也就是把model文件進行加載,用io流讀取二進制文件,最後存入到byte[]數組中/將文件傳入java的NDK接口(c++ 代碼中的init接口)/圖像輸入處理,調用mobileNetssd.Detect(input_bmp);方法得到目標座標xywh,畫出目標框/build之後得到自己的這個文件夾的對應的編譯硬件架構的文件夾下成功生成.so文件,名字爲libMobileNetssd.so
3、飛控PID算法
控制中心單片機通過IMU陀螺儀加速度計(MPU6050等等)獲取四旋翼的角度(俯仰、橫滾和偏航)的相對基準角度變化、然後濾波(卡爾曼濾波等等)處理獲得方向餘弦矩陣和四元數得到歐拉角、使用PID控制或者PI,PD控制(P比例I積分D微分)將系統反饋值和期望值進行比較、並根據偏差不斷修復、直至達到期望的預定值。P的作用是加快系統達到預期的速度;I的作用是消除淨差;D有阻尼的作用、就是阻止系統突變。
通過PID自動控制算法處理、輸出期望的PWM波給四個電調、控制四個無刷電機的轉速、從而得到一個期望的力控制四旋翼的前後左右上下飛行。
卡爾曼濾波原理:https://blog.csdn.net/tiandijun/article/details/72469471
四元數原理:https://www.zhihu.com/question/23005815
4、安卓系統的input子系統, led子系統
input子系統:https://www.cnblogs.com/haiming/p/3318614.html
led子系統:https://blog.csdn.net/qq_23327993/article/details/86520216
5、串口通信
串口通信
參考:https://blog.csdn.net/huwei2003/article/details/36418471
串口是串行接口(serial port)的簡稱,也稱爲串行通信接口或COM接口。
串口通信是指採用串行通信協議(serial communication)在一條信號線上將數據一個比特一個比特地逐位進行傳輸的通信模式。串口按電氣標準及協議來劃分,包括RS-232-C、RS-422、RS485等。
IIC
參考:https://blog.csdn.net/u010650845/article/details/73467586
概念:由數據線 SDA和時鐘線SCL兩根線構成的串行總線,可發送和接收數據。在CPU與被控IC之間、IC與IC之間進行雙向傳送
IIC數據傳輸速率有標準模式(100 kbps)、快速模式(400 kbps)和高速模式(3.4 Mbps),另外一些變種實現了低速模式(10 kbps)和快速+模式(1 Mbps)
uart串口通信
參考:https://blog.csdn.net/weixin_43664511/article/details/103038303
UART是一種通用串行數據總線,用於異步通信。該總線雙向通信,可以實現全雙工傳輸和接收。串口的通信方式爲串行通信,按位發送和接收字節,將並行數據轉換爲串行數據流發送出去,將接受的串行數據流轉換爲並行數據。完成串口通信,只需要三根線,接收(rx)、發送(tx)和地線。確定通信雙方的波特率和數據格式一致,是實現串口通信的前提。=0 BY-SA版權協議
算法題
給你一個單鏈表的鏈表頭,實現鏈表的排序,說出具體過程
參考:https://blog.csdn.net/baidu_30000217/article/details/77823084
交換節點:插入排序,冒泡排序,簡單選擇排序
交換數據:快速排序
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
//節點結構
struct node
{
int val;
struct node * next;
};
typedef struct node node, * list;
//打印函數
void printList(list mylist);
//排序函數
//插入排序
void insertSort(list mylist);
//冒泡排序
void bubbleSort(list mylist);
//簡單選擇
void selectSort(list mylist);
//快速排序
void quickSort(list mylist);
int main(void)
{
int arr[] = {5, 1, 7, 4, 2, 9, 6, 3, 8};
//程序都是針對有頭結點的單向鏈表
list mylist = (list)malloc(sizeof(node));
mylist -> val = 0;
mylist -> next = NULL;
int len = sizeof(arr) / sizeof(arr[0]);
int i = 0;
node * cur = mylist;
while(i < len)
{
node * newNode = (node *)malloc(sizeof(node));
newNode -> val = arr[i];
newNode -> next = NULL;
cur -> next = newNode;
cur = cur -> next;
i ++;
}
//insertSort(mylist);
//bubbleSort(mylist);
//selectSort(mylist);
quickSort(mylist);
printList(mylist);
return 0;
}
void printList(list mylist)
{
node * cur = mylist -> next;
while(cur != NULL)
{
printf("%d ", cur -> val);
cur = cur -> next;
}
printf("\n");
}
插入排序
//=============插入排序====================
void insertSort(list mylist)
{
if((mylist -> next == NULL) || (mylist -> next -> next == NULL))
{
return;
}
node * head, * p1, * prep1, * p2, * prep2, * temp;
head = mylist;
prep1 = head -> next;
p1 = prep1 -> next;
//prep1和p1是否需要手動後移
bool flag;
while(p1 != NULL)
{
flag = true;
temp = p1;
//由於是單向鏈表,所以只能從頭部開始檢索
for(prep2 = head, p2 = prep2 -> next; p2 != p1; prep2 = prep2 -> next, p2 = p2 -> next)
{
//發現第一個較大值
if(p2 -> val > p1 -> val)
{
p1 = p1 -> next;
prep1 -> next = p1;
prep2 -> next = temp;
temp -> next = p2;
flag = false;
break;
}
}
//手動後移prep1和p1
if(flag)
{
prep1 = prep1 -> next;
p1 = p1 -> next;
}
}
}
//=============插入排序====================
冒泡排序
//=============冒泡排序====================
void bubbleSort(list mylist)
{
if((mylist -> next == NULL) || (mylist -> next -> next == NULL))
{
return;
}
node *head, * pre, * cur, *next, * end, * temp;
head = mylist;
end = NULL;
//從鏈表頭開始將較大值往後沉
while(head -> next != end)
{
for(pre = head, cur = pre -> next, next = cur -> next; next != end; pre = pre -> next, cur = cur -> next, next = next -> next)
{
//相鄰的節點比較
if(cur -> val > next -> val)
{
cur -> next = next -> next;
pre -> next = next;
next -> next = cur;
temp = next;
next = cur;
cur = temp;
}
}
end = cur;
}
}
//=============冒泡排序====================
快速排序:https://blog.csdn.net/u012658346/article/details/51141288
C/C++實現strcpy和strcat兩個功能
strcpy實現
char* myStrcpy(char* pre, const char* next)
{
if (pre == nullptr || next == nullptr) //空指針直接返回
{
return nullptr;
}
if (pre == next) // 兩者相等也無需拷貝了
return pre;
while ((*pre++ = *next++) != '\0'); // 依次賦值給主字符數組
return pre;
}
//另一種形式,這種標準點
char* _strcpy(char* dest, const char* src) {
assert(dest != NULL && src != NULL);
char* temp = dest;
while (*src != '\0')
{
*dest = *src;
dest++;
src++;
}
*dest = '\0';
return dest;
}
int main() {
char s2[] = "efieji";
char s1[] = "123";
_strcpy(s2, s1);
cout << s2 << endl;
cout << strlen(s2) << endl;
}
strcat實現
char* myStrcat(char* pre, const char* next)
{
if (pre == nullptr || next == nullptr) // 如果有一個爲空指針,直接返回pre
return pre;
char* tmp_ptr = pre + strlen(pre); //strlen計算字符數,需要包含都文件string.h,當然也可以自己實現
while ( (*tmp_ptr++ = *next++) != '\0'); // 依次接着賦值
return pre;
}
#include<iostream>
#include<string>
#include<string.h>
using namespace std;
char* myStrcat(char* pre, const char* next)
{
if (pre == nullptr || next == nullptr)
return pre;
char* tmp_ptr = pre + strlen(pre);
while ( (*tmp_ptr++ = *next++) != '\0');
return pre;
}
char* myStrcpy(char* pre, const char* next)
{
if (pre == nullptr || next == nullptr)
{
return nullptr;
}
if (pre == next)
return pre;
while ((*pre++ = *next++) != '\0');
return pre;
}
int main()
{
char str1[100] = "12345";
char str2[20] = "hello world";
myStrcat(str1, str2);
myStrcpy(str1, str2);
printf("%s\n", str1);
return 0;
}
求矩陣對角線的和
求n階方陣的對角線元素之和。編寫主程序,用戶輸入矩陣的階數n,動態申請n*n的存儲空間,再輸入n行、n列的元素,調用函數求矩陣的對角元素之和,在主函數中輸出這個和。設元素均爲整數。n>=1。
//編寫一個程序輸入一個n*n的矩陣,求出兩條對角線元素值之和
#include <iostream>
using namespace std;
int main() {
int num;
cout << "請輸入對角矩陣的大小" << endl;
cin >> num;
int **p = new int*[num];//數組大小動態,二維
for (int i = 0; i <num; i++)
p[i] = new int[num];
int sum = 0;
int number;
for (int i = 0; i < num; i++) {
cout << "請輸入矩陣元素";
cout << "第" << (i + 1) << "行" << endl;
for (int j = 0; j < num; j++){
cout << "第" << (j + 1) << "列" << endl;
cin >> number;
p[i][j] = number;
}
}
for (int i = 0; i < num; i++) {
sum += p[i][i] + p[i][num-i-1];//兩個對角線元素相加
}
cout << "sum=" << sum << endl;
return 0;
}
C/C++/Python
python中set dict list tuple的區別
序列是Python中最基本的數據結構。序列中的每個元素都分配一個數字 - 它的位置,或索引,第一個索引是0,第二個索引是1,依此類推。Python有6個序列的內置類型,但最常見的是列表list和元組tuple。具體的不想講了,就總結一下吧,已經很晚了呢。
各自的特點:
list(列表):有序集合,隨時增刪
set(集合):無序集合、key不重複
tuple(元組):有序列表,一旦初始化,無法修改
dict(字典):鍵值對(key-value)方式存儲,查找速度快
總結:
1、list、tuple是有序列表;dict、set是無序列表
2、list元素可變、tuple元素不可變
3、dict和set的key值不可變,唯一性
4、set只有key沒有value
5、set的用途:去重、並集、交集等
6、list、tuple:+、*、索引、切片、檢查成員等
7、dict查詢效率高,但是消耗內存多;list、tuple查詢效率低、但是消耗內存少
一個C++源文件從文本到可執行文件經歷的過程
1).預處理,產生.ii文件
2).編譯,產生彙編文件(.s文件)
3).彙編,產生目標文件(.o或.obj文件)
4).鏈接,產生可執行文件(.out或.exe文件)
C/C++內存分佈?堆棧的區別?
棧,就是那些由編譯器在需要的時候分配,在不需要的時候自動清除的變量的存儲區。裏面的變量通常是局部變量、函數參數等。在一個進程中,位於用戶虛擬地址空間頂部的是用戶棧,編譯器用它來實現函數的調用。和堆一樣,用戶棧在程序執行期間可以動態地擴展和收縮。
堆,就是那些由 new 分配的內存塊,他們的釋放編譯器不去管,由我們的應用程序去控制,一般一個 new 就要對應一個 delete。如果程序員沒有釋放掉,那麼在程序結束後,操作系統會自動回收。堆可以動態地擴展和收縮。
自由存儲區,就是那些由 malloc 等分配的內存塊,他和堆是十分相似的,不過它是用 free 來結束自己的生命的。
全局/靜態存儲區,全局變量和靜態變量被分配到同一塊內存中,在以前的 C 語言中,全局變量又分爲初始化的和未初始化的(初始化的全局變量和靜態變量在一塊區域,未初始化的全局變量與靜態變量在相鄰的另一塊區域,同時未被初始化的對象存儲區可以通過 void* 來訪問和操縱,程序結束後由系統自行釋放),在 C++ 裏面沒有這個區分了,他們共同佔用同一塊內存區。
常量存儲區,這是一塊比較特殊的存儲區,他們裏面存放的是常量,不允許修改(當然,你要通過非正當手段也可以修改,而且方法很多
堆和棧的區別
首先是管理方式不同,堆由程序員負責申請和釋放,棧是編譯器負責的,然後是結構不同,堆是一種從底部往頂部擴展的結構,棧是從頂部到底部剛好相反的結構,最後是效率有很大的不同,堆的申請和釋放都需要經過算法計算,因爲要減少內存碎片和提高內存使用率,而棧由編輯器負責,速度非常快,在這點上堆的效率比較低
python3.6中下列程序的輸出是什麼,具體的題目記不清楚了,在這裏補充一下python2 和python3在輸出上的區別
1.python3中print是一個內置函數,有多個參數,而python2中print是一個語法結構;
2.Python2打印時可以不加括號:print 'hello world', Python3則需要加括號 print("hello world")
3.Python2中,input要求輸入的字符串必須要加引號,爲了避免讀取非字符串類型發生的一些行爲,不得不使用raw_input()代替input()
C語言中的malloc/free與C++中的new/delete的區別
new/delete是C++的操作符,而malloc/free是C中的函數。new做兩件事,一是分配內存,二是調用類的構造函數;同樣,delete會調用類的析構函數和釋放內存。而malloc和free只是分配和釋放內存。new建立的是一個對象,而malloc分配的是一塊內存;new建立的對象可以用成員函數訪問,不要直接訪問它的地址空間;malloc分配的是一塊內存區域,用指針訪問,可以在裏面移動指針;new出來的指針是帶有類型信息的,而malloc返回的是void指針。new/delete是保留字,不需要頭文件支持;malloc/free需要頭文件庫函數支持。
具體看下面:
1.所屬語言
new是C++特性,malloc是C的。C++一般使用的new,但也可以使用malloc,而C用malloc、realloc、calloc。
2.申請釋放方式
new和delete,malloc和free配對使用。new的使用比malloc簡單,內部已經實現了大小的計算、類型轉換等工作,而malloc使用時需要計算大小及進行類型轉換。
3.malloc是標準庫函數,new是C++的運算符。
new可以被重載,但malloc不可以,malloc需要庫函數的支持,new不需要。
4.構造與析構
new和delete會自動調用構造函數和析構函數,但是malloc和free不會。
5.申請內存失敗
申請內存失敗,默認new拋出異常,malloc返回NULL。
6.重新分配內存
malloc可利用realloc重新分配內存,new不可以。
7.類型安全性
new會檢查類型是否對應,如果不對應會保存,但malloc只關注申請內存的多少,不會檢查類型。
8.類型轉換
malloc返回的類型是void,所以在調用malloc時要進行顯式的類型轉換,將void轉換成所需的指針類型,new不需要。
9.數組分配
new有明確的方式處理數組的分配,即new[],釋放也有delete[],malloc沒有。
10.設置內存分配器
new可以設置自己的內存分配器,malloc不可以。
堆棧是什麼?局部變量和全局變量的存放位置
申請方式
棧:由系統自動分配,例如,生命在函數中一個局部變量 int b;系統自動在棧中爲b開闢空間;
堆:由程序員自己申請,並指明大小。例如:
在C中malloc函數
如p1 = (char *)malloc(10);
在C++中用new運算符
如p2 = (char *)malloc(10);
但是注意p1、p2本身是在棧中的。
申請後系統的反應
棧:只要棧的剩餘空間大於所申請空間,系統將爲程序提供內存,否則將報異常提示棧溢出。
堆:首先應該知道操作系統有一個記錄空閒內存地址的鏈表,當系統收到程序的申請時, 會遍歷該鏈表,尋找第一個空間大於所申請空間的堆結點,然後將該結點從空閒結點鏈表中刪除,並將該結點的空間分配給程序,另外,對於大多數系統,會在這塊內存空間中的首地址處記錄本次分配的大小,這樣,代碼中的delete語句才能正確的釋放本內存空間。另外,由於找到的堆結點的大小不一定正好等於申請的大小,系統會自動的將多餘的那部分重新放入空閒鏈表中。
申請大小的限制
棧:在Windows下,棧是向低地址擴展的數據結構,是一塊連續的內存的區域。這句話的意思是棧頂的地址和棧的最大容量是系統預先規定好的,在 WINDOWS下,棧的大小是2M(也有的說是1M,總之是一個編譯時就確定的常數),如果申請的空間超過棧的剩餘空間時,將提示overflow。因此,能從棧獲得的空間較小。
堆:堆是向高地址擴展的數據結構,是不連續的內存區域。這是由於系統是用鏈表來存儲的空閒內存地址的,自然是不連續的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限於計算機系統中有效的虛擬內存。由此可見,堆獲得的空間比較靈活,也比較大。
申請效率的比較
棧由系統自動分配,速度較快。但程序員是無法控制的。
堆是由new分配的內存,一般速度比較慢,而且容易產生內存碎片,不過用起來最方便.
另外,在WINDOWS下,最好的方式是用VirtualAlloc分配內存,他不是在堆,也不是在棧是直接在進程的地址空間中保留一快內存,雖然用起來最不方便。但是速度快,也最靈活。
存儲內容上
棧:在函數調用時,第一個進棧的是主函數中後的下一條指令(函數調用語句的下一條可執行語句)的地址,然後是函數的各個參數,在大多數的C編譯器中,參數是由右往左入棧的,然後是函數中的局部變量。注意靜態變量是不入棧的。
當本次函數調用結束後,局部變量先出棧,然後是參數,最後棧頂指針指向最開始存的地址,也就是主函數中的下一條指令,程序由該點繼續運行。
堆:一般是在堆的頭部用一個字節存放堆的大小。堆中的具體內容有程序員安排。
sizeof/strlen?在對字符串數組求sizeof的時候算不算最後的\0?
sizeof
a---數組名單獨出現,代表整個數組。int a[5] = {1,2,3,4};printf("%d\n",sizeof(a));//20
&a—表示的是整個數組的地址(地址的大小爲4,單位爲字節。)printf("%d\n",sizeof(a+0));//4 printf("%d\n",sizeof(&a+1));//4
解引用 *&a---代表整個數組的內容printf("%d\n",sizeof(*&a));//20,*&a---代表整個數組的內容,所以爲5*4
strlen
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", strlen(arr));//隨機值
因爲在此數組中沒有’ \0 ’,函數無法知道在哪裏停下來。printf("%d\n",strlen(&arr+1));//隨機值-6.但&數組名加上其他操作符—代表整個數組的地址。再接着執行。跳過整個數組,所以大小差6.
char arr[] = "abcdef";
printf("%d\n", strlen(arr));//6
printf("%d\n", strlen(arr+0));//6
二維數組
int a[3][4] = {0};
printf("%d\n",sizeof(a));//48 a---數組名單獨出現,代表整個數組。一共有12個元素,每個元素爲4字節。12*4
printf("%d\n",sizeof(*a));//16二維數組降級變爲一維數組,成爲第一行。所以第一行內容的大小爲4*4
printf("%d\n",sizeof(a[3]));//16表示第三行的數組長度,元素個數爲4,4*4
在對字符串數組求sizeof的時候算不算最後的\0?答案是:算。
sizeof 運算符能得出靜態數組的長度。與'\0'沒有關係的。
與'\0' 相關的是strlen函數 遇到'\0'就結束了。可以查看strlen實現。
舉個栗子:
char c[] = "abcde"
這是一個字符串常量,會隱式的在末尾加上空字符'\0';
sizeof 計算出來的是 6
strlen 算出來是5
函數指針
參考:https://blog.csdn.net/qq_28386947/article/details/73480831?depth_1-utm_source=distribute.pc_relevant.none-task-blog-OPENSEARCH-4&utm_source=distribute.pc_relevant.none-task-blog-OPENSEARCH-4
函數指針: 是指向函數的指針變量
在程序運行中,函數是程序算法指令部分,他們和數組一樣也佔用內存空間,也都有相應的地址。我們程序員可以使用指針變量指向數組的首地址,同樣,也可以使用指針變量指向函數代碼的首地址, 指向函數代碼首地址的指針變量,稱爲函數指針。
函數指針有兩個用途:調用函數和做函數的參數
函數指針和指針函數的區別:函數指針是一個指向函數的指針。他的本質是一個指針,而指針函數只是說明他是一個返回值爲指針的函數,它本質是一個函數,
類與結構體的區別
參考:https://blog.csdn.net/yuechuxuan/article/details/81673953
類是一種“引用類型”。創建類的對象時,對象賦值到的變量只保存對該內存的引用。將對象引用賦給新變量時,新變量引用的是原始對象。通過一個變量做出的更改將反映在另一個變量中,因爲兩者引用同一數據。
結構是一種值類型。創建結構時,結構賦值到的變量保存該結構的實際數據。將結構賦給新變量時,將複製該結構。因此,新變量和原始變量包含同一數據的兩個不同的副本。對一個副本的更改不影響另一個副本。
類通常用於對較爲複雜的行爲建模,或對要在創建類對象後進行修改的數據建模。結構最適合一些小型數據結構,這些數據結構包含的數據以創建結構後不修改的數據爲主。結構與類共享大多數相同的語法,但結構比類受到的限制更多。
C++中的struct對C中的struct進行了擴充,它已經不再只是一個包含不同數據類型的數據結構了,它已經獲取了太多的功能。
struct能包含成員函數嗎? 能!struct能繼承嗎? 能!!struct能實現多態嗎? 能!!!既然這些它都能實現,那它和class還能有什麼區別?最本質的一個區別就是默認的訪問控制:默認的繼承訪問權限,struct是public的,class是private的。
struct作爲數據結構的實現體,它默認的數據訪問控制是public的,而class作爲對象的實現體,它默認的成員變量訪問控制是private的
我依舊強調struct是一種數據結構的實現體,雖然它是可以像class一樣的用。我依舊將struct裏的變量叫數據,class內的變量叫成員,雖然它們並無區別。
“class”這個關鍵字還用於定義模板參數,就像“typename”。但關鍵字“struct”不用於定義模板參數。這一點在Stanley B.Lippman寫的Inside the C++ Object Model有過說明。
從上面的區別,我們可以看出,struct更適合看成是一個數據結構的實現體,class更適合看成是一個對象的實現體。
main的參數的意義
main()函數稱之爲主函數,一個C程序總是從main()函數開始執行的
main()函數的返回值類型是int型的,而程序最後的 return 0; 正與之遙相呼應,0就是main()函數的返回值。那麼這個0返回到那裏呢?返回給操作系統,表示程序正常退出。因爲return語句通常寫在程序的最後,不管返回什麼值,只要到達這一步,說明程序已經運行完畢。而return的作用不僅在於返回一個值,還在於結束函數。
C編譯器允許main()函數沒有參數,或者有兩個參數(有些實現允許更多的參數,但這只是對標準的擴展)。這兩個參數,一個是int類型,一個是字符串類型。第一個參數是命令行中的字符串數。按照慣例(但不是必須的),這個int參數被稱爲argc(argument count)。大家或許現在才明白這個形參爲什麼要取這麼個奇怪的名字吧,呵呵!至於英文的意思,自己查字典吧。第二個參數是一個指向字符串的指針數組。命令行中的每個字符串被存儲到內存中,並且分配一個指針指向它。按照慣例,這個指針數組被稱爲argv(argument value)。系統使用空格把各個字符串格開。一般情況下,把程序本身的名字賦值給argv[0],接着,把最後的第一個字符串賦給argv[1],等等。
TYPEDEF的用法
用來聲明自定義數據類型,主要是爲了達到簡便,還有就是在換編譯環境時這東西比較有用.因爲不同的編譯環境數據類型佔的位數有可能不一樣的.
全局變量,局部變量,靜態局部變量各是存放在哪個位置?
全局變量靜態局部變量: 存放在靜態RAM中.
局部變量:存放在動態區.而這東西在函數調用時會壓棧的.他可能這一次運行存放在一個地址,下一次運行時就在另一個地址了,是一個不固定的.寫過彙編的都知道.
回調函數以及用處,什麼時候執行?
回調函數就是一個通過函數指針調用的函數。如果你把函數的指針(地址)作爲參數傳遞給另一個函數,當這個指針被用爲調用它所指向的函數時,我們就說這是回調函數。回調函數不是由該函數的實現方直接調用,而是在特定的事件或條件發生時由另外的一方調用的,用於對該事件或條件進行響應。
ASSETR是什麼?什麼情況下需要用
提示你的代碼在錯誤的情況執行了.在你認爲正常情況下不可能執行到的地方就得放這東西..這東西寫代碼的人要記得經常用,多用.沒了
TYPEDEF的用法
用來聲明自定義數據類型,主要是爲了達到簡便,還有就是在換編譯環境時這東西比較有用.因爲不同的編譯環境數據類型佔的位數有可能不一樣的.
全局變量,局部變量,靜態局部變量各是存放在哪個位置?
全局變量靜態局部變量: 存放在靜態RAM中.
局部變量:存放在動態區.而這東西在函數調用時會壓棧的.他可能這一次運行存放在一個地址,下一次運行時就在另一個地址了,是一個不固定的.寫過彙編的都知道.
兩個函數怎麼共享資源
可以用全局變量,不夠由於沒有約束,會使得函數變得不可重入,也可以使用指針,以參數的形式傳遞一個指針,就可以共享指針所指向的資源
const和define的區別,分別在什麼情況下使用
在編譯器的角度下,const其實給出了地址,而define給出了立即數,然後const是有類型的,表達式運算的時候會進行類型安全檢查,而define沒有,最後const只在第一次使用的時候訪問內存,往後的使用都是訪問符號表,define則是普通的字符串替換,具有多個副本。
const一般用於函數的參數保護,以免函數內部不小心修改了只讀變量,define其實比較自由,按照Linux的風格,define是有崇高的地位,很多短小精悍的功能都由define完成,如果就定義常量而言,const和define都可以,不過我個人認爲const定義的常量比define要高效
下面的聲明都是什麼意思?
const int a;
int const a;
const int *a;
int *const a;
int const *a const;
前兩個的作用是一樣,a是一個常整型數。第三個意味着a是一個指向常整型數的指針(也就是,整型數是不可修改的,但指針可以)。第四個意思a是一個指向整型數的常指針(也就是說,指針指向的整型數是可以修改的,但指針是不可修改的)。最後一個意味着a是一個指向常整型數的常指針(也就是說,指針指向的整型數是不可修改的,同時指針也是不可修改的)。如果應試者能正確回答這些問題,那麼他就給我留下了一個好印象。順帶提一句,也許你可能會問,即使不用關鍵字 const,也還是能很容易寫出功能正確的程序,那麼我爲什麼還要如此看重關鍵字const呢?我也如下的幾下理由:
1). 關鍵字const的作用是爲給讀你代碼的人傳達非常有用的信息,實際上,聲明一個參數爲常量是爲了告訴了用戶這個參數的應用目的。如果你曾花很多時間清理其它人留下的垃圾,你就會很快學會感謝這點多餘的信息。(當然,懂得用const的程序員很少會留下的垃圾讓別人來清理的。)
2). 通過給優化器一些附加的信息,使用關鍵字const也許能產生更緊湊的代碼。
3). 合理地使用關鍵字const可以使編譯器很自然地保護那些不希望被改變的參數,防止其被無意的代碼修改。簡而言之,這樣可以減少bug的出現。
volatile的作用
編譯器優化常用的方法有:將內存變量緩存到寄存器;調整指令順序充分利用CPU。
volatile的本意是“易變的” 因爲訪問寄存器要比訪問內存單元快的多,所以編譯器一般都會作減少存取內存的優化,但有可能會讀髒數據(髒數據就是在物理上臨時存在過,但在邏輯上不存在的數據)。當要求使用volatile聲明變量值的時候,系統總是重新從它所在的內存讀取數據,即使它前面的指令剛剛從該處讀取過數據。精確地說就是,遇到這個關鍵字聲明的變量,編譯器對訪問該變量的代碼就不再進行優化,從而可以提供對特殊地址的穩定訪問;如果不使用valatile,則編譯器將對所聲明的語句進行優化。(簡潔的說就是:volatile關鍵詞影響編譯器編譯的結果,用volatile聲明的變量表示該變量隨時可能發生變化,與該變量有關的運算,不要進行編譯優化,以免出錯)
關鍵字volatile有什麼含意 並給出三個不同的例子?
一個定義爲volatile的變量是說這變量可能會被意想不到地改變,這樣,編譯器就不會去假設這個變量的值了。精確地說就是,優化器在用到這個變量時必須每次都小心地重新讀取這個變量的值,而不是使用保存在寄存器裏的備份。下面是volatile變量的幾個例子:
1). 並行設備的硬件寄存器(如:狀態寄存器)
2). 一箇中斷服務子程序中會訪問到的非自動變量(Non-automatic variables)
3). 多線程應用中被幾個任務共享的變量
硬件、中斷、RTOS等等打交道,所用這些都要求volatile變量。不懂得volatile內容將會帶來災難。
假設被面試者正確地回答了這是問題(嗯,懷疑這否會是這樣),我將稍微深究一下,看一下這傢伙是不是直正懂得volatile完全的重要性。
1). 一個參數既可以是const還可以是volatile嗎?解釋爲什麼。
2). 一個指針可以是volatile 嗎?解釋爲什麼。
3). 下面的函數有什麼錯誤:
int square(volatile int *ptr)
{ return *ptr * *ptr;
}
下面是答案:
1). 是的。一個例子是隻讀的狀態寄存器。它是volatile因爲它可能被意想不到地改變。它是const因爲程序不應該試圖去修改它。
2). 是的。儘管這並不很常見。一個例子是當一箇中服務子程序修該一個指向一個buffer的指針時。
3). 這段代碼的有個惡作劇。這段代碼的目的是用來返指針*ptr指向值的平方,但是,由於*ptr指向一個volatile型參數,編譯器將產生類似下面的代碼:
int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
由於*ptr的值可能被意想不到地該變,因此a和b可能是不同的。結果,這段代碼可能返不是你所期望的平方值!正確的代碼如下:
long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}
static關鍵字
靜態局部變量使用static修飾符定義,即使在聲明時未賦初值,編譯器也會把它初始化爲0。且靜態局部變量存儲於進程的全局數據區,即使函數返回,它的值也會保持不變。
1)在函數體,一個被聲明爲靜態的變量在這一函數被調用過程中維持其值不變。
2) 在模塊內(但在函數體外),一個被聲明爲靜態的變量可以被模塊內所用函數訪問,但不能被模塊外其它函數訪問。它是一個本地的全局變量。
3) 在模塊內,一個被聲明爲靜態的函數只可被這一模塊內的其它函數調用。那就是,這個函數被限制在聲明它的模塊的本地範圍內使用。
指針和引用的區別
對象是指一塊能存儲數據並具有某種類型的內存空間,一個對象a,它有值和地址&a,運行程序時,計算機會爲該對象分配存儲空間,來存儲該對象的值,我們通過該對象的地址,來訪問存儲空間中的值
指針p也是對象,它同樣有地址&p和存儲的值p,只不過,p存儲的數據類型是數據的地址。如果我們要以p中存儲的數據爲地址,來訪問對象的值,則要在p前加解引用操作符"*",即*p。
對象有常量(const)和變量之分,既然指針本身是對象,那麼指針所存儲的地址也有常量和變量之分,指針常量是指,指針這個對象所存儲的地址是不可以改變的,而指向常量的指針的意思是,不能通過該指針來改變這個指針所指向的對象。
引用可以理解成變量的別名。定義一個引用的時候,程序把該引用和它的初始值綁定在一起,而不是拷貝它。計算機必須在聲明r的同時就要對它初始化,並且,r一經聲明,就不可以再和其它對象綁定在一起了。引用的一個優點是它一定不爲空,因此相對於指針,它不用檢查它所指對象是否爲空,這增加了效率。
C++多態的實現方式
1、覆蓋
覆蓋是指子類重新定義父類的虛函數的做法。當子類重新定義了父類的虛函數後,父類指針根據賦值給它的不同的子類指針,動態的調用屬於子類的該函數,這樣函數調用在編譯期間是無法確定的,這樣的函數地址在運行期間綁定稱爲動態聯編。
2、重載
重載是指允許存在多個同名函數,而這些函數的參數表不同(或許參數個數不同,或許參數類型不同,或許兩者都不同)。編譯器根據函數不同的參數表,對同名函數的名稱做修飾,然後這些同名函數就成了不同的函數。
智能指針
shared_ptr、unique_ptr、weak_ptr
shared_ptr多個指針指向相同的對象。shared_ptr使用引用計數,每一個shared_ptr的拷貝都指向相同的內存。每使用他一次,內部的引用計數加1,每析構一次,內部的引用計數減1,減爲0時,自動刪除所指向的堆內存。shared_ptr內部的引用計數是線程安全的,但是對象的讀取需要加鎖。
- 初始化。智能指針是個模板類,可以指定類型,傳入指針通過構造函數初始化。也可以使用make_shared函數初始化。不能將指針直接賦值給一個智能指針,一個是類,一個是指針。例如std::shared_ptr<int> p4 = new int(1);的寫法是錯誤的
- 拷貝和賦值。拷貝使得對象的引用計數增加1,賦值使得原對象引用計數減1,當計數爲0時,自動釋放內存。後來指向的對象引用計數加1,指向後來的對象。
- get函數獲取原始指針
- 注意不要用一個原始指針初始化多個shared_ptr,否則會造成二次釋放同一內存
- 注意避免循環引用,shared_ptr的一個最大的陷阱是循環引用,循環,循環引用會導致堆內存無法正確釋放,導致內存泄漏。循環引用在weak_ptr中介紹。
int main()
{
shared_ptr<Test> ptest(new Test("123"));
shared_ptr<Test> ptest2(new Test("456"));
cout<<ptest2->getStr()<<endl;
cout<<ptest2.use_count()<<endl;
ptest = ptest2;//"456"引用次數加1,“123”銷燬
ptest->print();
cout<<ptest2.use_count()<<endl;//2
cout<<ptest.use_count()<<endl;//2
ptest.reset();
ptest2.reset();//此時“456”銷燬
cout<<"done !\n";
return 0;
}
unique_ptr“唯一”擁有其所指對象,同一時刻只能有一個unique_ptr指向給定對象(通過禁止拷貝語義、只有移動語義來實現)。相比與原始指針unique_ptr用於其RAII的特性,使得在出現異常的情況下,動態資源能得到釋放。unique_ptr指針本身的生命週期:從unique_ptr指針創建時開始,直到離開作用域。離開作用域時,若其指向對象,則將其所指對象銷燬(默認使用delete操作符,用戶可指定其他操作)。unique_ptr指針與其所指對象的關係:在智能指針生命週期內,可以改變智能指針所指對象,如創建智能指針時通過構造函數指定、通過reset方法重新指定、通過release方法釋放所有權、通過移動語義轉移所有權。
weak_ptr是用來解決shared_ptr相互引用時的死鎖問題,如果說兩個shared_ptr相互引用,那麼這兩個指針的引用計數永遠不可能下降爲0,資源永遠不會釋放。它是對對象的一種弱引用,不會增加對象的引用計數,和shared_ptr之間可以相互轉化,shared_ptr可以直接賦值給它,它可以通過調用lock函數來獲得shared_ptr。
class B;
class A
{
public:
shared_ptr<B> pb_;
~A()
{
cout<<"A delete\n";
}
};
class B
{
public:
shared_ptr<A> pa_;
~B()
{
cout<<"B delete\n";
}
};
void fun()
{
shared_ptr<B> pb(new B());
shared_ptr<A> pa(new A());
pb->pa_ = pa;
pa->pb_ = pb;
cout<<pb.use_count()<<endl;
cout<<pa.use_count()<<endl;
}
int main()
{
fun();
return 0;
}
C++程序設計中使用堆內存是非常頻繁的操作,堆內存的申請和釋放都由程序員自己管理。程序員自己管理堆內存可以提高了程序的效率,但是整體來說堆內存的管理是麻煩的,C++11中引入了智能指針的概念,方便管理堆內存。使用普通指針,容易造成堆內存泄露(忘記釋放),二次釋放,程序發生異常時內存泄露等問題等,使用智能指針能更好的管理堆內存。
理解智能指針需要從下面三個層次:
從較淺的層面看,智能指針是利用了一種叫做RAII(資源獲取即初始化)的技術對普通的指針進行封裝,這使得智能指針實質是一個對象,行爲表現的卻像一個指針。
智能指針的作用是防止忘記調用delete釋放內存和程序異常的進入catch塊忘記釋放內存。另外指針的釋放時機也是非常有考究的,多次釋放同一個指針會造成程序崩潰,這些都可以通過智能指針來解決。
智能指針還有一個作用是把值語義轉換成引用語義。
用預處理指令#define 聲明一個常數,用以表明1年中有多少秒(忽略閏年問題)
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
寫一個"標準"宏MIN ,這個宏輸入兩個參數並返回較小的一個。
#define MIN(A,B) ((A) <= (B) ? (A) : (B))
用變量a給出下面的定義
a) 一個整型數(An integer)
b)一個指向整型數的指針( A pointer to an integer)
c)一個指向指針的的指針,它指向的指針是指向一個整型數( A pointer to a pointer to an intege)r
d)一個有10個整型數的數組( An array of 10 integers)
e) 一個有10個指針的數組,該指針是指向一個整型數的。(An array of 10 pointers to integers)
f) 一個指向有10個整型數數組的指針( A pointer to an array of 10 integers)
g) 一個指向函數的指針,該函數有一個整型參數並返回一個整型數(A pointer to a function that takes an integer as an argument and returns an integer)
h) 一個有10個指針的數組,該指針指向一個函數,該函數有一個整型參數並返回一個整型數( An array of ten pointers to functions that take an integer argument and return an integer )
答案是:
a) int a; // An integer
b) int *a; // A pointer to an integer
c) int **a; // A pointer to a pointer to an integer
d) int a[10]; // An array of 10 integers
e) int *a[10]; // An array of 10 pointers to integers
f) int (*a)[10]; // A pointer to an array of 10 integers
g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer
h) int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer
單片機嵌入式
中斷與異常有何區別
異常在處理的時候必須考慮與處理器的時鐘同步,實際上異常也稱爲同步中斷,在處理器執行到因編譯錯誤而導致的錯誤指令時,或者在執行期間出現特殊錯誤,必須靠內核處理的時候,處理器就會產生一個異常;所謂中斷是指外部硬件產生的一個電信號從CPU的中斷引腳進入,打斷CPU的運行。所謂異常是指軟件運行過程中發生了一些必須作出處理的事件,CPU自動產生一個陷入來打斷CPU的運行。
簡述SPI,UART,I2C三種傳輸方式
SPI:高速同步串行口,首發獨立,可同步進行
SPI接口主要應用在EEPROM,Flash,實時時鐘,A/D轉化器,數字信號處理,是一種全雙工同步通訊總線,該接口一般使用四條線:串行時鐘線(sck),主出從入線,主入從出線,低電平有效地的從機選擇線。
I2C協議:是單片機與其他芯片進行通訊的協議:
A、只要求兩條總線線路,一條是串行時鐘線,一條是串行數據線;
B、通過軟件設定地址
C、是一個多主機總線,如果兩個或更多主機同時初始化數據傳送可通過沖突檢測和仲裁防止數據破壞;
D、I2C總線傳輸的是數據的總高位
UART:主要是由一個modem(調制解調器),可以將模擬信號量轉化成數字信號量。
嵌入式操作系統和通用操作系統有什麼差別?
答案:多優先級,搶佔型,實時操作系統。嵌入式操作系統一般沒有UI,體積小,實時性強,對穩定性要求更高。嵌入式操作系統強調實時性,並且可裁減。要求系統資源的消耗要儘可能的小。
ARM指令體系
arm和Thumb
什麼叫波特率?和比特率有什麼區別?
波特率是傳輸一個碼元的時間,而比特率是傳輸一個二進制位的時間,區別在於,一個碼元不一定是一個二進制位(雖然經常都是),比如當傳輸的狀態有4種的時候,需要2個二進制位表示全部狀態,此時發送一個狀態爲一個碼元,一個碼元其實就是2個二進制位,此時波特率是比特率的兩倍,一個通用的公式是比特率=波特率*一個碼元對應的二進制位數
PWM有佔空比的概念,講一講佔空比
佔空比就是在一個脈衝寬度中,通電時間或者高電平時間所佔的比例
單片機怎麼實現佔空比
可以用定時功能實現,設置好脈衝寬度和佔空比後,計算定時的溢出時間,這段時間就作爲高電平的輸出時間,其餘時間輸出低電平
怎麼實現定時功能
單片機都會有定時器,用定時器和中斷就可以了
講一講中斷的概念
中斷是用於處理緊急事件的,系統只要預先設置好中斷源、中斷觸發方式、中斷服務函數、中斷屏蔽位等等就可以使用中斷了,當中斷源滿足中斷條件的時候就會觸發中斷,此時CPU會停止當前工作,然後保護現場,接着跳轉到中斷服務函數執行,最後恢復現場
中斷觸發方式有哪些
外部中斷一般是上升沿、下降沿或者兩者都觸發,而內部中斷是由程序行爲觸發的,比如未定義行爲像數組越界、除數是0等等
假設時鐘頻率128MHz,那麼1us可以運行多少條指令?
時鐘週期:時鐘週期也稱爲振盪週期,定義爲時鐘脈衝的倒數(時鐘週期就是單片機外接晶振的倒數,例如12M的晶振,它的時鐘週期就是1/12us),是計算機中的最基本的、最小的時間單位。在一個時鐘週期內,CPU僅完成一個最基本的動作。時鐘脈衝是計算機的基本工作脈衝,控制着計算機的工作節奏。時鐘頻率越高,工作速度就越快。8051單片機把一個時鐘週期定義爲一個節拍(用P表示),二個節拍定義爲一個狀態週期(用S表示)。
機器週期:計算機中,常把一條指令的執行過程劃分爲若干個階段,每一個階段完成一項工作。每一項工作稱爲一個基本操作,完成一個基本操作所需要的時間稱爲機器週期。8051系列單片機的一個機器週期由6個S週期(狀態週期)組成。 一個S週期=2個節拍(P),所以8051單片機的一個機器週期=6個狀態週期=12個時鐘週期。例如外接24M晶振的單片機,他的一個機器週期=12/24M 秒;
指令週期:執行一條指令所需要的時間,一般由若干個機器週期組成。指令不同,所需的機器週期也不同。
簡述SPI,UART,I2C三種傳輸方式
SPI:高速同步串行口,首發獨立,可同步進行
SPI接口主要應用在EEPROM,Flash,實時時鐘,A/D轉化器,數字信號處理,是一種全雙工同步通訊總線,該接口一般使用四條線:串行時鐘線(sck),主出從入線,主入從出線,低電平有效地的從機選擇線。
I2C協議:是單片機與其他芯片進行通訊的協議:
A、只要求兩條總線線路,一條是串行時鐘線,一條是串行數據線;
B、通過軟件設定地址
C、是一個多主機總線,如果兩個或更多主機同時初始化數據傳送可通過沖突檢測和仲裁防止數據破壞;
D、I2C總線傳輸的是數據的總高位
UART:主要是由一個modem(調制解調器),可以將模擬信號量轉化成數字信號量。
嵌入式系統總是要用戶對變量或寄存器進行位操作。給定一個整型變量a,寫兩段代碼,第一個設置a的bit 3,第二個清除a 的bit 3。在以上兩個操作中,要保持其它位不變。
最佳的解決方案如下:
#define BIT3 (0x1 << 3)
static int a;
void set_bit3(void)
{
a |= BIT3;
}
void clear_bit3(void)
{
a &= ~BIT3;
}
linux操作系統
linux使用過哪些命令?
ls;cd;pwd;mkdir;rm;cp;chmod;ll;ifconfig;grep;ps;df;kil
linux調度算法有哪些?
SCHED_OTHER 分時調度策略,
SCHED_FIFO實時調度策略,先到先服務
SCHED_RR實時調度策略,時間片輪轉
多線程爲什麼要設置屬性,爲什麼要設置棧大小
我答的不夠用所以要分配更多的空間…後來百度了一下,linux線程默認分配8M的調用棧,那麼太多的線程會導致棧溢出的問題,所以可以調用pthread_attr_setstack自動分配合理的大小防止棧溢出。當然線程還有其他的一些屬性。
進程通信有哪些,如果我需要傳輸大量數據使用那種方式效率最高。
管道,命名管道,消息隊列,共享內存,信號量,socket unix域,數據很大的時候,用共享內存是最好的,不用從用戶態到內核態的頻繁切換和拷貝數據,直接從內存中讀取就可以,速度最快,但是共享內存開闢空間就必須要使用同步機制,這是他的缺點。交叉編譯鏈
gcc
在一種計算機環境(稱爲host machine)中運行的編譯程序,能編譯出在另外一種環境(稱爲target machine)下運行的代碼,叫做交叉編譯。實現這個交叉編譯的一系列工具,包括C函數庫,內核文件,編譯器,鏈接器,調試器,二進制工具……稱爲交叉編譯工具鏈。
實際上在進行嵌入式開發時,我們通常都會在主機上(host machine)使用開發板廠商提供的編譯器,調試器。比如在windows上裝環境調試51,61單片機,在Linux上用arm-gcc寫arm開發板的程序……
搭建交叉編譯環境是一個非常繁瑣並細緻的過程。筆者就已嵌入式Linux交叉編譯工具鏈的搭建爲例,介紹下交叉編譯工具的創建過程和原理。
線程和進程的區別
進程和線程的主要差別在於它們是不同的操作系統資源管理方式。進程有獨立的地址空間,一個進程崩潰後,在保護模式下不會對其它進程產生影響,而線程只是一個進程中的不同執行路徑。線程有自己的堆棧和局部變量,但線程之間沒有單獨的地址空間,一個線程死掉就等於整個進程死掉,所以多進程的程序要比多線程的程序健壯,但在進程切換時,耗費資源較大,效率要差一些。但對於一些要求同時進行並且又要共享某些變量的併發操作,只能用線程,不能用進程。
1) 簡而言之,一個程序至少有一個進程,一個進程至少有一個線程.
2) 線程的劃分尺度小於進程,使得多線程程序的併發性高。
3) 另外,進程在執行過程中擁有獨立的內存單元,而多個線程共享內存,從而極大地提高了程序的運行效率。
4) 線程在執行過程中與進程還是有區別的。每個獨立的線程有一個程序運行的入口、順序執行序列和程序的出口。但是線程不能夠獨立執行,必須依存在應用程序中,由應用程序提供多個線程執行控制。
5) 從邏輯角度來看,多線程的意義在於一個應用程序中,有多個執行部分可以同時執行。但操作系統並沒有將多個線程看做多個獨立的應用,來實現進程的調度和管理以及資源分配。這就是進程和線程的重要區別。
進程之間相互通信的方式
1. 管道pipe:管道是一種半雙工的通信方式,數據只能單向流動,而且只能在具有親緣關係的進程間使用。進程的親緣關係通常是指父子進程關係。
2. 命名管道FIFO:有名管道也是半雙工的通信方式,但是它允許無親緣關係進程間的通信。
4. 消息隊列MessageQueue:消息隊列是由消息的鏈表,存放在內核中並由消息隊列標識符標識。消息隊列克服了信號傳遞信息少、管道只能承載無格式字節流以及緩衝區大小受限等缺點。
5. 共享存儲SharedMemory:共享內存就是映射一段能被其他進程所訪問的內存,這段共享內存由一個進程創建,但多個進程都可以訪問。共享內存是最快的 IPC 方式,它是針對其他進程間通信方式運行效率低而專門設計的。它往往與其他通信機制,如信號兩,配合使用,來實現進程間的同步和通信。
6. 信號量Semaphore:信號量是一個計數器,可以用來控制多個進程對共享資源的訪問。它常作爲一種鎖機制,防止某進程正在訪問共享資源時,其他進程也訪問該資源。因此,主要作爲進程間以及同一進程內不同線程之間的同步手段。
7. 套接字Socket:套解口也是一種進程間通信機制,與其他通信機制不同的是,它可用於不同及其間的進程通信。
8. 信號 ( sinal ) : 信號是一種比較複雜的通信方式,用於通知接收進程某個事件已經發生。
網絡IO模式(select、epoll)
I/O 多路複用( IO multiplexing):select,poll,epoll,有些地方也稱這種IO方式爲event driven IO。select/epoll的好處就在於單個process就可以同時處理多個網絡連接的IO。它的基本原理就是select,poll,epoll這個function會不斷的輪詢所負責的所有socket,當某個socket有數據到達了,就通知用戶進程。但select,poll,epoll本質上都是同步I/O,因爲他們都需要在讀寫事件就緒後自己負責進行讀寫,也就是說這個讀寫過程是阻塞的,而異步I/O則無需自己負責進行讀寫,異步I/O的實現會負責把數據從內核拷貝到用戶空間。
int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
select 函數監視的文件描述符分3類,分別是writefds、readfds、和exceptfds。調用後select函數會阻塞,直到有描述副就緒(有數據 可讀、可寫、或者有except),或者超時(timeout指定等待時間,如果立即返回設爲null即可),函數返回。當select函數返回後,可以 通過遍歷fdset,來找到就緒的描述符。
poll
int poll (struct pollfd *fds, unsigned int nfds, int timeout);
不同與select使用三個位圖來表示三個fdset的方式,poll使用一個 pollfd的指針實現。
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events to watch */
short revents; /* returned events witnessed */
};
pollfd結構包含了要監視的event和發生的event,不再使用select“參數-值”傳遞的方式。同時,pollfd並沒有最大數量限制(但是數量過大後性能也是會下降)。 和select函數一樣,poll返回後,需要輪詢pollfd來獲取就緒的描述符。
epoll
epoll是在2.6內核中提出的,是之前的select和poll的增強版本。相對於select和poll來說,epoll更加靈活,沒有描述符限制。epoll使用一個文件描述符管理多個描述符,將用戶關係的文件描述符的事件存放到內核的一個事件表中,這樣在用戶空間和內核空間的copy只需一次。
Linux的進程調度(優先級、時間片輪轉調度)
1、先來先服務調度算法
先來先服務(FCFS)調度算法是一種最簡單的調度算法,該算法既可用於作業調度,也可用於進程調度。當在作業調度中採用該算法時,每次調度都是從後備作業隊列中選擇一個或多個最先進入該隊列的作業,將它們調入內存,爲它們分配資源、創建進程,然後放入就緒隊列。在進程調度中採用FCFS算法時,則每次調度是從就緒隊列中選擇一個最先進入該隊列的進程,爲之分配處理機,使之投入運行。該進程一直運行到完成或發生某事件而阻塞後才放棄處理機。
2、短作業(進程)優先調度算法
短作業(進程)優先調度算法,是指對短作業或短進程優先調度的算法。它們可以分別用於作業調度和進程調度。短作業優先(SJF)的調度算法是從後備隊列中選擇一個或若干個估計運行時間最短的作業,將它們調入內存運行。而短進程優先(SPF)調度算法則是從就緒隊列中選出一個估計運行時間最短的進程,將處理機分配給它,使它立即執行並一直執行到完成,或發生某事件而被阻塞放棄處理機時再重新調度。
3、時間片輪轉法
在早期的時間片輪轉法中,系統將所有的就緒進程按先來先服務的原則排成一個隊列,每次調度時,把CPU分配給隊首進程,並令其執行一個時間片。時間片的大小從幾ms到幾百ms。當執行的時間片用完時,由一個計時器發出時鐘中斷請求,調度程序便據此信號來停止該進程的執行,並將它送往就緒隊列的末尾;然後,再把處理機分配給就緒隊列中新的隊首進程,同時也讓它執行一個時間片。這樣就可以保證就緒隊列中的所有進程在一給定的時間內均能獲得一時間片的處理機執行時間。換言之,系統能在給定的時間內響應所有用戶的請求。
能說一下具體操作系統移植的過程麼?
答:內核啓動流程:uboot->kernel->rootfs uboot基本都不會做修改,直接拿來燒在板上即可,uboot主要在調試模式修改一些環境變量,包括tftp的下載地址(serverip、ipaddr),包括bootcmd傳給內核的參數,傳給根文件系統的參數bootargs,並熟悉相應的命令使用(nand scrub、set pri、boot);內核源碼編譯,內核參考開發板的參數進行修改(開發板和母版的參數差異),make menuconfig進行模塊選擇,ulmage的製作;然後內核利用bootargs參數進行根文件系統啓動,再就是根文件目錄樹的創建及其基本文件的創建。
(以上基本就能應付,很少有面試官深入,因爲應屆畢業生做arm移植的太少了=-=,更深的問題:爲什麼內核初始化地址是0x30008000,ulmage和zlmage的差別,make menuconfig添加模塊的依據,)
移植遇到比較困難的問題
我說我當時移植QT的庫時 ,發現版子上分區內存不夠,當時也不知道怎麼解決 就去學習分區掛載的知識,用df查看分區,發現有分區但是沒掛載,導致空間不夠,但是又不想在系統啓動每次手動去掛載,於是就研究根文件啓動init的過程,在配置文件inittab添加掛載命令,系統就會自啓動並掛載分區了。
接着問,如果開發板上只有十兆空間,但是你的移植的QT庫有二十兆,你會怎麼做?
我說的擴容,他說除了擴容呢,我接着說移植的文件中有一個stripped屬性,用strip命令去除庫文件中的符號表,會小很多,他說QT編譯一般會做這個工作,還有別的方法麼…我就答不上來了。查了一下看了別人的方法https://blog.csdn.net/yming0221/article/details/6548349
shell腳本
#!/bin/sh
cd ~
mkdir shell_tut
cd shell_tut
for ((i=0; i<10; i++)); do
touch test_$i.txt
done
示例解釋
第1行:指定腳本解釋器,這裏是用/bin/sh做解釋器的
第2行:切換到當前用戶的home目錄
第3行:創建一個目錄shell_tut
第4行:切換到shell_tut目錄
第5行:循環條件,一共循環10次
第6行:創建一個test_0…9.txt文件
第7行:循環體結束
mkdir, touch都是系統自帶的程序,一般在/bin或者/usr/bin目錄下。for, do, done是sh腳本語言的關鍵字。
計算機網絡
OSI七層網絡協議和TCP/IP協議
OSI是Open System Interconnection的縮寫,意爲開放式系統互聯。是設計和描述計算機網絡通信的基本框架。OSI模型把網絡通信的工作分爲7層
應用層(Application):提供網絡與用戶應用軟件之間的接口服務
表示層(Presentation):提供格式化的表示和轉換數據服務,如加密和壓縮
會話層(Session):提供包括訪問驗證和會話管理在內的建立和維護應用之間通信的機制
傳輸層(Transimission): 提供建立、維護和取消傳輸連接功能,負責可靠地傳輸數據(PC)
網絡層(Network):處理網絡間路由,確保數據及時傳送(路由器)
數據鏈路層(DataLink) :負責無錯傳輸數據,確認幀、發錯重傳等(交換機)
物理層(Physics) : 提供機械、電氣、功能和過程特性(網卡、網線、雙絞線、同軸電纜、中繼器)
TCP/IP協議簡單介紹
TCP(Transmission Control Protocol 傳輸控制協議)是一種面向連接的、可靠的、基於字節流的傳輸層通信協議,是互聯網的基礎,也是每個程序員必備的基本功。TCP/IP參考模型分爲四層,從上到下分別是:應用層、傳輸層、網絡互連層、網絡接口層。
TCP功能
將數據進行分段打包傳輸
對每個數據包編號控制順序
運輸中丟失、重發和丟棄處理
流量控制避免擁塞
TCP粘包、拆包及解決辦法
參考:https://zhuanlan.zhihu.com/p/108822858
UDP 是基於報文發送的,UDP首部採用了 16bit 來指示 UDP 數據報文的長度,因此在應用層能很好的將不同的數據報文區分開,從而避免粘包和拆包的問題。而 TCP 是基於字節流的,雖然應用層和 TCP 傳輸層之間的數據交互是大小不等的數據塊,但是 TCP 並沒有把這些數據塊區分邊界,僅僅是一連串沒有結構的字節流;另外從 TCP 的幀結構也可以看出,在 TCP 的首部沒有表示數據長度的字段,基於上面兩點,在使用 TCP 傳輸數據時,纔有粘包或者拆包現象發生的可能。
什麼是粘包、拆包?
假設 Client 向 Server 連續發送了兩個數據包,用 packet1 和 packet2 來表示,那麼服務端收到的數據可以分爲三種情況,現列舉如下:
第一種情況,接收端正常收到兩個數據包,即沒有發生拆包和粘包的現象。
第二種情況,接收端只收到一個數據包,但是這一個數據包中包含了發送端發送的兩個數據包的信息,這種現象即爲粘包。這種情況由於接收端不知道這兩個數據包的界限,所以對於接收端來說很難處理。
第三種情況,這種情況有兩種表現形式,如下圖。接收端收到了兩個數據包,但是這兩個數據包要麼是不完整的,要麼就是多出來一塊,這種情況即發生了拆包和粘包。這兩種情況如果不加特殊處理,對於接收端同樣是不好處理的。
爲什麼會發生 TCP 粘包、拆包?
要發送的數據大於 TCP 發送緩衝區剩餘空間大小,將會發生拆包。
待發送數據大於 MSS(最大報文長度),TCP 在傳輸前將進行拆包。
要發送的數據小於 TCP 發送緩衝區的大小,TCP 將多次寫入緩衝區的數據一次發送出去,將會發生粘包。
接收數據端的應用層沒有及時讀取接收緩衝區中的數據,將發生粘包。
粘包、拆包解決辦法
由於 TCP 本身是面向字節流的,無法理解上層的業務數據,所以在底層是無法保證數據包不被拆分和重組的,這個問題只能通過上層的應用協議棧設計來解決,根據業界的主流協議的解決方案,歸納如下:
消息定長:發送端將每個數據包封裝爲固定長度(不夠的可以通過補 0 填充),這樣接收端每次接收緩衝區中讀取固定長度的數據就自然而然的把每個數據包拆分開來。
設置消息邊界:服務端從網絡流中按消息邊界分離出消息內容。在包尾增加回車換行符進行分割,例如 FTP 協議。
將消息分爲消息頭和消息體:消息頭中包含表示消息總長度(或者消息體長度)的字段。
更復雜的應用層協議比如 Netty 中實現的一些協議都對粘包、拆包做了很好的處理。