一道Block面試題的深入挖掘

2021-03-01 知識小集

作者 | 收納箱,綠洲iOS研發工程師,綠洲ID:收納箱KeepFit

0. 序言

最近看到了一道Block的面試題,還蠻有意思的,來給大家分享一下。

本文從一道Block面試題出發,層層深入到達Block原理的講解,把面試題吃得透透的。

題外話:

很多人覺得Block的定義很怪異,很難記住。但其實和C語言的函數指針的定義對比一下,你很容易就可以記住。

// Block
returnType (^blockName)(parameterTypes)

// 函數指針
returnType (*c_func)(parameterTypes)

例如輸入和返回參數都是字符串:

(char *) (*c_func)(const char *);
(NSString *) (^block)(NSString *);

好了,下面正式開始~

1. 面試題1.1 問題1

以下代碼存在內存洩露麼?

• 不存在

• 存在

- (void)viewDidLoad {
[super viewDidLoad];
NSNotificationCenter *__weak center = [NSNotificationCenter defaultCenter];
id token = [center addObserverForName:UIApplicationDidEnterBackgroundNotification
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification * _Nonnull note) {
[self doSomething];
[center removeObserver:token];
}];
}

- (void)doSomething {

}

答案是存在!

1.1.1 分析

• block中,我們使用到的外部變量有self和center,center使用了__weak說明符肯定沒問題。

• center持有token,token持有block,block持有self,也就是說token不釋放,self肯定沒法釋放。

• 我們注意到[center removeObserver:token];這步會把token從center中移除掉。按理說,center和self是不是就可以被釋放了呢?

我們來看看編譯器怎麼說:

編譯器告訴我們,token在被block捕獲之前沒有初始化![center removeObserver:token];是沒法正確移除token的,所以self也沒法被釋放!

為什麼沒有被初始化?

因為token在後面的方法執行完才會被返回。方法執行的時候token還沒有被返回,所以捕獲到的是一個未初始化的值!

1.2 問題2

以下代碼存在內存洩露麼?

• 不存在
• 存在

- (void)viewDidLoad {
[super viewDidLoad];
NSNotificationCenter *__weak center = [NSNotificationCenter defaultCenter];
id __block token = [center addObserverForName:UIApplicationDidEnterBackgroundNotification
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification * _Nonnull note) {
[self doSomething];
[center removeObserver:token];
}];
}

- (void)doSomething {

}

這次代碼在token之前加入了__block說明符。

提示:這次編譯器沒有警告說token沒有被初始化了。

答案是還是存在!

1.2.1 分析

首先,證明token的值是正確的,同時大家也可以看到token確實是持有block的。

那麼,為什麼還會洩露呢?

因為,雖然center對token的持有已經沒有了,token現在還被block持有。

可能還有同學會問:

加入了__block說明符,token對象不是還是center返回之後才能拿到麼,為什麼加了之後就沒問題了呢?

原因會在Block原理部分詳細說明。

1.3 問題3

以下代碼存在內存洩露麼?

• 不存在

• 存在

- (void)viewDidLoad {
[super viewDidLoad];
NSNotificationCenter *__weak center = [NSNotificationCenter defaultCenter];
id __block token = [center addObserverForName:UIApplicationDidEnterBackgroundNotification
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification * _Nonnull note) {
[self doSomething];
[center removeObserver:token];
token = nil;
}];
}

- (void)doSomething {

}

- (void)dealloc {
NSLog(@"%s", __FUNCTION__);
}

答案是不存在!

1.3.1 分析

我們可以驗證一下:

可以看到,我們添加token = nil;之後,ViewController被正確釋放了。這一步,解除了token與block之間的循環引用,所以正確釋放了。

有人可能會說:

使用__weak typeof(self) wkSelf = self;就可以解決self不釋放的問題。

確實這可以解決self不釋放的問題,但是這裡仍然存在內存洩露!

2. Block的原理

雖然面試題解決了,但是還有幾個問題沒有弄清楚:

為什麼沒有__block說明符token未被初始化,而有這個說明符之後就沒問題了呢?
token和block為什麼會形成循環引用呢?

2.1 Block捕獲自動變量

剛剛的面試題比較複雜,我們先來看一個簡單的:

Block轉換為C函數之後,Block中使用的自動變量會被作為成員變量追加到 __X_block_impl_Y結構體中,其中 X一般是函數名, Y是第幾個Block,比如main函數中的第0個結構體: __main_block_impl_0。

typedef void (^MyBlock)(void);

int main(int argc, const char * argv[])
{
@autoreleasepool
{
int age = 10;
MyBlock block = ^{
NSLog(@"age = %d", age);
};
age = 18;
block();
}
return 0;
}

順便說一下,這個輸出:age = 10

在命令行中對這個文件進行一下處理:

clang -w -rewrite-objc main.m

或者

xcrun -sdk iphoneos clang -arch arm64 -w -rewrite-objc main.m

區別是下面指定了SDK和架構代碼會少一點。

處理完之後會生成一個main.cpp的文件,打開後會發現代碼很多,不要怕。搜索int main就能看到熟悉的代碼了。

int main(int argc, const char * argv[])
{
/* @autoreleasepool */
{ __AtAutoreleasePool __autoreleasepool;
int age = 10;
MyBlock block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
age = 18;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}

下面是main函數中涉及到的一些結構體:

struct __main_block_impl_0 {
struct __block_impl impl; //block的函數的imp結構體
struct __main_block_desc_0* Desc; // block的信息
int age; // 值引用的age值
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock; // 棧類型的block
impl.Flags = flags;
impl.FuncPtr = fp; // 傳入了函數具體的imp指針
Desc = desc;
}
};

struct __block_impl {
void *isa; // block的類型:全局、棧、堆
int Flags;
int Reserved;
void *FuncPtr; // 函數的指針!就是通過它調用block的!
};

static struct __main_block_desc_0 { // block的信息
size_t reserved;
size_t Block_size; // block的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

有了這些信息,我們再看看

MyBlock block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));

可以看到,block初始化的時候age是值傳遞,所以block結構體中age=10,所以列印的是age = 10。

2.2 __block說明符

Block中修改捕獲的自動變量有兩種方法:

• 使用靜態變量、靜態全局變量、全局變量

從Block語法轉化為C語言函數中訪問靜態全局變量、全局變量,沒有任何不同,可以直接訪問。而靜態變量使用的是靜態變量的指針來進行訪問。

自動變量不能採用靜態變量的做法進行訪問。原因是,自動變量是在存儲在棧上的,當超出其作用域時,會被棧釋放。而靜態變量是存儲在堆上的,超出作用域時,靜態變量沒有被釋放,所以還可以訪問。

• 添加 __block 修飾符

__block存儲域類說明符。存儲域說明符會指定變量存儲的域,如棧auto、堆static、全局extern,寄存器register。

比如剛剛的代碼加上 __block說明符:

typedef void (^MyBlock)(void);

int main(int argc, const char * argv[])
{
@autoreleasepool
{
int __block age = 10;
MyBlock block = ^{
age = 18;
};
block();
}
return 0;
}

在命令行中對這個文件進行一下處理:

xcrun -sdk iphoneos clang -arch arm64 -w -rewrite-objc main.m

我們看到main函數發生了變化:

• 原來的age變量:int age = 10;

• 現在的age變量:__Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};。

int main(int argc, const char * argv[])
{
/* @autoreleasepool */
{ __AtAutoreleasePool __autoreleasepool;
__Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
MyBlock block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}

原來我們知道添加 __block說明符,我們就可以在block裡面修改自動變量了。

恭喜你,現在你達到了第二層!__block說明符,其實會把自動變量包含到一個結構體中。

這也就解釋了問題1為什麼加入__block說明符,token可以正確拿到值。

MyBlock block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));

這次block初始化的過程中,把age這個結構體傳入到了block結構體中,現在就變成了指針引用。

struct __Block_byref_age_0 {
void *__isa; //isa指針
__Block_byref_age_0 *__forwarding; // 指向自己的指針
int __flags; // 標記
int __size; // 結構體大小
int age; // 成員變量,存儲age值
};

struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_age_0 *age; // 結構體指針引用
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

我們再來看看block中是如何修改age對應的值:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_age_0 *age = __cself->age; // 通過結構體的self指針拿到age結構體的指針
(age->__forwarding->age) = 18; // 通過age結構體指針修改age值
}

看到這裡可能不明白__forwarding的作用,我們之後再講。現在知道是age是指針引用修改成功的就可以了。

2.3 Block存儲域

從C代碼中我們可以看到Block的是指是Block結構體實例,__block變量實質是棧上__block變量結構體實例。從初始化函數中我們可以看到,impl.isa = &_NSConcreteStackBlock;,即之前我們使用的是棧Block。

其實,Block有3中類型:

• _NSConcreteGlobalBlock類對象存儲在程序的數據區(.data區)。

• _NSConcreteStackBlock類對象存儲在棧上。

• _NSConcreteMallocBlock類對象存儲在堆上。

void (^blk)(void) = ^{
NSLog(@"Global Block");
};

int main() {
blk();
NSLog(@"%@",[blk class]);//列印:__NSGlobalBlock__
}

全局Block肯定是存儲在全局數據區的,但是在函數棧上創建的Block,如果沒有捕獲自動變量,Block的結構實例還是 _NSConcreteGlobalBlock,而不是 _NSConcreteStackBlock:

void (^blk0)(void) = ^{ // 沒有截獲自動變量的Block
NSLog(@"Stack Block");
};
blk0();
NSLog(@"%@",[blk0 class]); // 列印:__NSGlobalBlock__

int i = 1;
void (^blk1)(void) = ^{ // 截獲自動變量i的Block
NSLog(@"Capture:%d", i);
};
blk1();
NSLog(@"%@",[blk1 class]); // 列印:__NSMallocBlock__

可以看到沒有捕獲自動變量的Block列印的類是NSGlobalBlock,表示存儲在全局數據區。但為什麼捕獲自動變量的Block列印的類卻是設置在堆上的NSMallocBlock,而非棧上的NSStackBlock?這個問題稍後解釋。

設置在棧上的Block,如果超出作用域,Block就會被釋放。若 __block變量也配置在棧上,也會有被釋放的問題。所以, copy方法調用時,__block變量也被複製到堆上,同時impl.isa = &_NSConcreteMallocBlock;。複製之後,棧上 __block變量的__forwarding指針會指向堆上的對象。因 此 __block變量無論被分配在棧上還是堆上都能夠正確訪問。

編譯器如何判斷何時需要進行copy操作呢?

在ARC開啟時,自動判斷進行 copy:

• 手動調用copy。

• 將Block作為函數參數返回值返回時,編譯器會自動進行 copy。

• 將Block賦值給 copy修飾的id類或者Block類型成員變量,或者__strong修飾的自動變量。

• 方法名含有usingBlock的Cocoa框架方法或GCD相關API傳遞Block。

如果不能自動 copy,則需要我們手動調用 copy方法將其複製到堆上。比如向不包括上面提到的方法或函數的參數中傳遞Block時。

ARC環境下,返回一個對象時會先將該對象複製給一個臨時實例指針,然後進行retain操作,再返回對象指針。runtime/objc-arr.mm提到,Block的retain操作objc_retainBlock函數實際上是Block_copy函數。在實行retain操作objc_retainBlock後,棧上的Block會被複製到堆上,同時返回堆上的地址作為指針賦值給臨時變量。

2.4 __block變量存儲域

當Block從棧複製到堆上時候,__block變量也被複製到堆上並被Block持有。

• 若此時 __block變量已經在堆上,則被該Block持有。

• 若配置在堆上的Block被釋放,則它所持有的 __block變量也會被釋放。

__block int val = 0;
void (^block)(void) = [^{ ++val; } copy];
++val;
block();

利用 copy操作,Block和 __block變量都從棧上被複製到了堆上。無論是{ ++val; }還是++val;都轉換成了++(val->__forwarding->val);。

Block中的變量val為複製到堆上的 __block變量結構體實例,而Block外的變量val則為複製前棧上的 __block變量結構體實例,但這個結構體的__forwarding成員變量指向堆上的 __block變量結構體實例。所以,無論是是在Block內部還是外部使用 __block變量,都可以順利訪問同一個 __block變量。

3. 面試題C代碼

下面我們看看面試題的C代碼。

@interface Test : NSObject
@end
@implementation Test
- (void)test_notification {
NSNotificationCenter *__weak center = [NSNotificationCenter defaultCenter];
id __block token = [center addObserverForName:@"com.demo.perform.once"
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification * _Nonnull note) {
[self doSomething];
[center removeObserver:token];
token = nil;
}];
}
- (void)doSomething {

}
@end

3.1 重寫

在命令行中對這個文件進行一下處理,因為用到了 __weak說明符,需要額外指定一些參數:

xcrun -sdk iphoneos clang -arch arm64 -w -rewrite-objc -fobjc-arc -mios-version-min=8.0.0 -fobjc-runtime=ios-8.0.0 main.m

這個會更複雜一些,但我們只看重要的部分:

struct __Block_byref_token_0 {
void *__isa;
__Block_byref_token_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
__strong id token; // id類型的token變量 (strong)
};

struct __Test__test_notification_block_impl_0 {
struct __block_impl impl;
struct __Test__test_notification_block_desc_0* Desc;
Test *const __strong self; // 被捕獲的self (strong)
NSNotificationCenter *__weak center; // center對象 (weak)
__Block_byref_token_0 *token; // token結構體的指針
__Test__test_notification_block_impl_0(void *fp, struct __Test__test_notification_block_desc_0 *desc, Test *const __strong _self, NSNotificationCenter *__weak _center, __Block_byref_token_0 *_token, int flags=0) : self(_self), center(_center), token(_token->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

現在我們看到block結構體 __Test__test_notification_block_impl_0中持有token,同時之前我們看到token也是持有block的,所以造成了循環引用。

這也就回答了問題2。

下面我們看看block的IMP函數是如何解決循環引用問題的:

static void __Test__test_notification_block_func_0(struct __Test__test_notification_block_impl_0 *__cself, NSNotification * _Nonnull __strong note) {
__Block_byref_token_0 *token = __cself->token; // bound by ref
Test *const __strong self = __cself->self; // bound by copy
NSNotificationCenter *__weak center = __cself->center; // bound by copy

((void (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("doSomething"));
((void (*)(id, SEL, id _Nonnull __strong))(void *)objc_msgSend)((id)center, sel_registerName("removeObserver:"), (id)(token->__forwarding->token));
(token->__forwarding->token) = __null;
}

可以看到,token = nil;被轉換為了(token->__forwarding->token) = __null;,相當於block對象對token的持有解除了!如果你覺得看不太明白,我再轉換一下:

(__cself->token->__forwarding->token) = __null; // __cself為block結構體指針

3.2 Block的類型

細心的同學可能發現:

impl.isa = &_NSConcreteStackBlock;

這是一個棧類型的block呀,聲明周期結束不是就該被系統回收釋放了麼。我們使用了ARC同時我們調用是方法名中含有usingBlock,會主動觸發 copy操作,將其複製到堆上。

4. 總結

Block最常問的就是循環引用、內存洩露問題。

注意要點:

• __weak說明符的使用

• __block說明符的使用

• 誰持有誰

• 如何解除循環引用

另外,需要再強調一下的是:

• 面試題中的block代碼如果一次都沒有執行也是會內存洩露的!

• 可能有人會說使用__weak typeof(self) wkSelf = self;就可以解決self不釋放的問題。
確實這可以解決self不釋放的問題,但是這裡 仍然存在內存洩露!我們還是需要從根上解決這個問題。

補充

上面講的時候集中在說token和block的循環引用,ViewController的問題我簡單帶過了,可能同學們看的時候沒有注意到。

我在這裡專門拎出來說一下:

token和block循環引用,同時block持有self(ViewController),導致ViewController也沒法釋放。
如果希望優先釋放ViewController(不管block是否執行),最好給ViewController加上__weak說明符。

此外,破除token和block的循環引用,實際有兩種方法:

• 手動設置token = nil;。

• token也使用__weak說明符id __block __weak token。

• 

以下說法不夠嚴謹,也可能存在問題:

最簡單粗暴的解決辦法:大家都__weak。

NSNotificationCenter *__weak wkCenter = [NSNotificationCenter >defaultCenter];
__weak typeof(self) wkSelf = self;
id __block __weak wkToken = [wkCenter addObserverForName:UIApplicationDidEnterBackgroundNotification
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification * _Nonnull note) {
[wkSelf doSomething];
[wkCenter removeObserver:wkToken];
}];

這個問題具體要看NSNotificationCenter具體是怎麼實現的。token使用__weak說明符,但是如果NSNotificationCenter沒有持有token,在函數作用域結束時,token會被銷毀。雖然不會有循環引用問題,但是可能導致無法移除這個觀察者的問題。


就差您點一下了 👇👇👇

相關焦點

  • 這是一道有趣兒的面試題
    孔叔偶然看到的一道產品經理面試小明要喝果汁,媽媽沒空,怎麼解決?面試官更願意在這樣一個生活性的面試題上看到作為產品經理多角度思考問題的能力。既然要解決這樣的問題,我們首先要結合用戶場景和需求痛點出發,以產品經理的思維解決問題。
  • ​挖掘三點共線向量等式所隱藏的兩個結論 簡解一道向量題
    挖掘三點共線向量等式所隱藏的兩個結論簡解一道向量題浙江省平陽中學        洪一平甘肅省蘭州市       王 冰 分析:解決本題的關鍵是對向量等式ABsinA+ACsinC=AQ所隱含的信息的挖掘和利用。挖掘信息的多少及質量直接影響解題的繁與簡。你能直接從這個向量等式得到哪些結論?
  • [重拾CSS]一道面試題來看偽元素、包含塊和高度坍塌
    前言前幾天某個群友在群裡問了一道面試題,就是關於一個自適應的正方形布局的困惑,先貼上代碼。我其實很長一段時間沒有寫 CSS 了,對於裡面的一些細節也比較模糊了,因此決定重拾 CSS,來重新捋一捋這題目中的一些知識點。
  • 公考面試的經典陷阱:一道題兩個問號有玄機
    在面試中經常出現一道題有兩個或三個問題, 如某機關單位, 進行機關服務作風調查, 由你負責, 你的方案是什麼? 你怎麼看點? 對這樣的問題, 要根據每個問題來分別回答, 不能將兩個問題融在一起。 因為考官在面試四五個人之後, 會進入一種迷茫的狀態,聽一會兒, 走走神, 如果將兩個問題融在一起回答, 會使考官認為你的回答不完整。
  • Excel_一道組合公式的有趣面試題
    1 序言  本篇介紹一道我以前在廣州面試數據分析,某知名教育機構的上機題  「題目:要求用Excel公式,實現圖中的數字的規律展示」2 找規律  一般這種規律題,我會以兩種思路進行分析:一是從兩邊即首行首列,一是從對角線入手。
  • 智力題:一道面試題測試你的臨場反應能力,你的大腦靈活嗎?
    智力題是一種很有趣的題目,智力題的目的不是為了檢測一個人學習成績的好壞以及智商的高低,而是檢測一個人的天分。說簡單點智力題能讓每個人通過做題發覺自己的天分,從而找到最適合自己的行業。大家都知道孔聖人曾說過教學要因人而異,讓每個孩子能找到最適合自己的路,而智力題無疑就是找到這條路的最佳鑰匙。
  • 每周·面試題 | Python 5題快問快答!
    夢想橡皮擦 | 作者CSDN | 來源https://blog.csdn.net/hihell/article/details/88808395第1題:Python2和Python3的range(100)的區別
  • 一道網紅面試題(騰訊、百度面試中都出現過)
    記得先點web前端學習圈關注我哦~在騰訊和百度的面試中,出現了這樣一道面試題,被大家親切的稱呼為網紅面試題,這道面試題就是。['1', '2', '3'].map(parseInt)的輸出結果是什麼?['1', '2', '3'].fliter(parseInt)的輸出結果是什麼?
  • 從一道面試題談談一線碼農應該具備的基本素質
    舉個簡單的例子, 面試官出一道題目, 候選人 A 可能曾經做過或見過, 所以能夠比較輕鬆地回答出這個問題, 而候選人 B 沒有做過, 雖然不能答出讓面試官滿意的答案, 但 B 提供了一些解題的思路, 雖然最終並沒有答出這道題目, 這就一定說明候選人 B 比 A 差麼? 並不見得.
  • 公務員面試、事業單位面試幾類熱點題,是基礎知識,可支撐許多題
    關於這方面的面試題的回答,是「人人心中有,個個口中無的」面試題,確實也是對考生理論素質和表達能力的深度考察。能夠把堅定理想信念不動搖、堅持理論學習不動搖、堅持政治忠誠不動搖、堅持政令暢通不動搖說出來,再能夠解釋一下就能夠把這方面問題的思路撐開了。如果把衡陽賄選案等等案例再有機地結合進來,那就比較鮮活了。
  • 青島二中自招面試首次加入英語,一道大題取代兩張卷
    原標題:青島二中自招面試首次加入英語,一道大題取代兩張卷 14、15日,2018青島市高中自主招生筆試、面試相繼進行。今年自招變化最大的當屬青島二中,筆試環節二中只考了一道項目題,題目圍繞「在某個地方修建機場」展開,下設的5個小題需要運用數學、化學、物理、生物、地理等多學科知識來解答。
  • 一道趣味面試題,絕大多數人都是「複製」答案,千篇一律照貓畫虎
    好像是2012年南京大學自主招生的一道趣味面試題,用4個0來算24點你會算嗎?南京是一個風景秀美的好地方,長江、紫金山、玄武湖……只是曾經被日本來的那夥強盜燒殺搶掠無惡不作的令人髮指的行徑所汙染過,總是會令人想起曾經悲慘的歷史,國人當自強不息,中華民族當讓四方來賀。說到南京就會想起歷史,下面回到此篇。
  • 從一道面試題談談一線大廠碼農應該具備的基本能力
    舉個簡單的例子,面試官出一道題目,候選人 A 可能曾經做過或見過,所以能夠比較輕鬆地回答出這個問題,而候選人 B 沒有做過,雖然不能答出讓面試官滿意的答案,但 B 提供了一些解題的思路,雖然最終並沒有答出這道題目,這就一定說明候選人 B 比 A 差麼? 並不是吧。下面就從這道題目說起,這道題目是我在過往的面試中經常考察的一道題目。
  • 每日一練 | Data Scientist & Business Analyst & Leetcode 面試題 1043
    Data Application Lab 自2017年6月15日起,每天和你分享討論一道數據科學(DS)和商業分析(BA)領域常見的面試問題。
  • 一道面試題,看一線碼農應該具備的基本素質
    (算上校招的話更多), 各種各樣的人都遇到過,雖然做面試官經驗不是很多,但這裡也想談談自己的一些看法。面試本來就是一個雙向選擇的過程, 面試官和候選人的地位本應該是一個平等的位置,面試官希望通過簡單的交流溝通可以對候選人的技術、溝通等(可能主要是技術)有一定了解進而確定候選人是否匹配相應的職位。因為面試時間有限,很難去全面了解候選人的技術實力,所以在面試過程中很難做到完全公平。舉個簡單的例子。
  • 一道Python面試題:如何反轉字符串
    (點擊上方公眾號,可快速關注一起學Python)來源:公眾號-哎媽呀Bug  連結:https://mp.weixin.qq.com/s/wC5x8fRAwVvZYs3YGVr5iw按單詞反轉字符串是一道很常見的面試題
  • 高效「背誦」面試題的三定法則
    不難發現: 題目1是有固定答案的封閉式面試題; 題目2開放式題目,側重考你的理解深度; 題目3就是典型的邏輯算法題了。 因此,在你「背誦」面試題的第一步,你首先要搞清楚的就是題目類型。
  • 一道神奇的Python面試題,你會嗎?
    無意間,看到這麼一道Python面試題:以下代碼將輸出什麼?是在考面試者閉包相關知識以及Python 的閉包的後期綁定問題麼?若將題目改成:以下代碼輸出的結果是(0,2,4,6)麼?如果不是,你將會怎麼做,讓它變成(0,2,4,6)?這樣會不會更有意思點呢?歡迎大家出妙招,看究竟有多少招?(哈哈哈!!!)特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺「網易號」用戶上傳並發布,本平臺僅提供信息存儲服務。
  • 拼多多2020屆數據分析面試題合集
    問了一道sql相關的題 大概是說用了group by做提取的時候有時候到進度條最後會卡住 問原因 一開始沒回答上來 面試官特別耐心給了點提示 最後我說大概就是先group by兩個欄位之後再匯總的方法吧 面試官可能覺得可行?
  • iOS面試指南(2020年6月)參考答案
    關於面試題 打個比方,如果把找工作理解成考大學,面試就是高考,市面上的「真題」就是模擬試卷。我們會很容易傾向於在面試前尋找對應公司的面試「真題」,重點準備,期待「押題」成功。但實際上,即使面試同一家公司,它會有不同部門,不同業務線,不同面試官,即使遇到同一面試官,他也不一定就每次考察完全一樣的內容。