回望2022,拒絕間接性努力【面試題第一彈】

沃和萊特發表於2023-01-29

本文參與了 SegmentFault 思否年度徵文「一名技術人的 2022」,歡迎正在閱讀的你也加入。

前言

寫這篇文章的時候已經是年初四,臨近2023年的時候就已經看到了身邊的同學陸續發出自己的年度總結,而自己雖是百感交集,但卻遲遲下不了筆。直到今天才有了這篇刪刪減減的遲來的2022年度總結。

圖片.png

回望2022

說到2022年的關鍵詞——行程碼,健康碼,核酸全員檢測,陽了個陽...

但讓我感觸最深的是還是 “陽”,當朋友圈裡滿屏曬著自己的“陽過”圖時,還有希望進入決賽圈的我一直心存著所謂“天選之子”的僥倖,但是不知為什麼,卻又想讓自己被這個可怕的病毒盯上。當我真正感染上以後,才迷迷糊糊的知道自己為什麼有“想要感染”的想法了————感染過後,原本都是早晨六點半起床開始新的學習計劃的我硬是被鬧鐘叫到了七點半;原本是晚上十點半才結束一天的“知識更新”的安排也被打亂,不到九點就合上電腦開啟手機刷起短影片來...就這樣,將近一個月沒有更新部落格。

這種“光明正大”把自己放進舒適圈,然後竟少了那麼多焦慮和負罪感,而我想要自己“陽”的真正目的其實是想要讓自己有個能騙過自己的理由去鬆懈。

陽康後,真的發現自己有很長一段時間在間歇性努力,給自己創造“自我感動”的假象————想要考研,但備考計劃卻是斷斷續續的完成;想要更新技術部落格,但更新一篇文章後,即使有了靈感也沒有及時記錄下來。偶然在逛論壇的時候發現一位博主寫的年度總結中,就有一個詞:間歇性努力。這讓我有種被驚醒的感覺,我翻開自己的部落格主頁,發現最新的文章已經是一個月前,在草稿箱裡面還存放著沒有完善好的文章以及斷斷續續的經驗總結。

間歇性努力往往比堅持帶來的短暫快樂容易上癮,並且自己努力的上限也會越來越低,而結果通常是惡性迴圈和事半功倍。

展望2023

2022年發現自己有了間歇性努力,2023年要讓自己在該補充知識能量的時候跳出“舒適圈”。希望自己能夠在新的一年“卯”足幹勁,“兔”破困難。

面試第一彈

學習面試題,總結面試經驗也是2023年的一個flag吧,這篇文章給大家帶來的還有自己在學習上遇到的面試問題。

題目描述

對於堆和棧你是如何理解的,最好是用一個簡單的程式描述一下。

問題剖析

堆和棧的問題可以說是在計算機的學習中很常見的了,特別是在資料結構中,“堆”,“棧”絕對是經常出現的字眼,首先我們來簡單回顧一下堆和棧是什麼,然後可以從兩者的區別聯絡方面來回答這個問題,並利用一個簡單的c++程式來描述一下:

,是動態分配記憶體的一種儲存形式,隨意讀取且方便。
可以看成一組陣列物件以二叉樹的形態分佈,執行時動態分配記憶體,對讀取順序無限制。

,是一種只能後進先出讀取的線性表,讀取順序限制性強。並且僅能在表尾進行插入和刪除操作的線性表,遵循後進先出的原則。

問題解答

堆和棧的區別與聯絡:

是一種動態的概念,它的大小是受動態變化的,取決於程式執行的那一刻所計算的資料,其訪問速度相對於棧來說比較慢;堆是在程式中進行的,並且不是獨立程式,因此所有的執行緒都可以對堆進行訪問,從訪問許可權上來說堆是不安全的。

是一種靜態概念,它的大小是在編譯時由程式設計師確定好的;比如函式的呼叫就是在棧上進行的,當一個函式被呼叫後,裡面的許可權是無法被另一個棧訪問的,也就是說不同函式之間的棧資料是無法共享的,那麼在安全性上就要比堆安全

堆和棧的優缺點

說到這裡,堆和棧的優缺點應該也能答出來一二了吧:


優點:堆申請的空間大;可以動態地分配記憶體大小
缺點:記憶體洩漏。如果由於某種原因程式沒有釋放,就會造成記憶體洩漏,出現記憶體浪費;比如在c++中,我們使用new來建立一個物件時,返回的是一個物件指標,這個指標指向本類剛建立的這個物件。在c++中分配給指標的只有儲存指標值的空間,但物件所佔用的空間分配在堆上,因此使用new來建立物件時,必須使用delete來釋放記憶體。
易產生記憶體碎片。前面說到了堆的處理效率是比較低的,因此就會產生很多自定義的記憶體碎片;
執行緒不安全。因為堆不是獨立程式,在程式執行過程中其他的程式可以對其進訪問。


優點:執行緒獨立安全性較高;棧資料是可以共享;記憶體可以及時得到回收,記憶體更好管理。
缺點:棧獲得的空間較小,存在棧中的資料大小和生存期必須是確定的,資料的固定性也讓棧缺乏靈活性。
棧溢位問題(stack overflow)由於棧的大小是有限的,當出現區域性陣列過大或遞迴呼叫層次過多,會超出棧的容量;並且棧上的buffer是大小固定的,如果指標或者是陣列越界的話,就會影響到棧上原有的資料,造成棧溢位問題。

圖片.png

程式描述

這裡用到了c++中的解構函式來簡單解釋一下堆和棧(當然在實際開發當中堆和棧遠比這要複雜的多,舉個簡單的小例子比較容易懂):

using namespace std;
class A
{
  public:
    A()
  {  
    cout<<"A的建構函式"  <<endl; 
  }
    ~A()
  {
    cout<<"A的解構函式"<<endl;
  }
};
class B
{
  A *a;//在B中定義一個指標 
  public:
    B()
  {
    cout<<"B的建構函式"<<endl;
    a=new A;//指標指向堆區空間 ,使用new生成物件指標時,自動呼叫本類的建構函式 
  }
    ~B()
  {
    cout<<"B的解構函式" <<endl;
    delete a;//使用關鍵字new 建立的物件,用delete來撤銷 ;使用delete刪除這個物件時,首先呼叫本類的解構函式,再釋放佔用的記憶體。 
  }
}; 
int main()
{
  B b;//B的物件b定義在棧區,出棧之後自動呼叫本類的解構函式 
  return 0;
}

執行結果:
圖片.png

假如將“delete a;”註釋掉,那麼就會出現記憶體洩露的情況,而解構函式可以清理類中有指標、並且指向堆區空間的成員,釋放指標指向的堆區空間,防止記憶體洩露。
圖片.png

假如我們要使用記憶體龐大的資料或者是資料大小不確定的時候可以使用堆;如果資料記憶體較小的話,我們可以使用棧。

簡單補充:
在js的資料型別中,我們知道有基本資料型別和引用資料型別,其中
基本資料型別: Number、String、Boolean、Undefined、Null、Symbol(es6新增的原始資料型別)是儲存在中的,基本資料型別大小固定,佔據空間小,被頻繁使用。它的賦值方式是深複製。
引用資料型別: object、 array、 function 存在中。當我們需要訪問這三種引用型別的值時,首先得從棧中獲得該物件的地址指標,然後再從堆記憶體中取得所需的資料。它的資料型別是淺複製。

舉個例子:

//基本資料型別(深複製,存放在棧)
var a=10;
var b=a;
console.log(b);//輸出10
// 引用資料型別(淺複製,存放在堆)
var a=Object();//a的資料型別是object
var b=a;
b.num="20";//b的num屬性新增到堆記憶體中,實際上a和b共同指向了一個堆記憶體物件
console.log(a.num);//輸出20

總結

首先祝大家兔年快樂,新的一年越戰越勇。也希望自己在2023年突破舒適圈拒絕間歇性努力

相關文章