C++模拟面试:宏、lambda、智能指针闲谈

简介: C++模拟面试:宏、lambda、智能指针闲谈

有时候出于种种目的,我们会用宏来写一些函数。有人称之为宏函数。下面我们来模拟一场面试:


微信图片_20220528184041.jpg面试官

                                     自来也


先来个简单的热热身,用宏实现求两个数最大值。


刷刷刷


#define MAX(x, y) ((x) > (y) ? (x) : (y))

调用的时候:


int m = MAX(1, 10);
double m1 = MAX(1.0, 10.0)


借助三目运算符,这种一行的表达式很方便的可以做成宏函数。其实我觉得叫做函数宏可能更准确。因为从偏正短语的文法角度讲,『宏』才是中心词,应该放后面。好了,不上语文课了。


为什么我想强调宏,而不是函数。因为并不是所有带括号的宏,都能当成『函数』使用。比如:

#define FOO(name, score) score_map[name]=score; if (score > max_score) max_score = score;


宏就是机械地展开,带上参数,也就是增加了一点变量的味道,但并不是所有带参数的宏都能模拟『返回值』的效果,所以也就不是所有带参数的宏,都能当成宏函数……所谓的宏函数其实是一种巧合。


看到这里,你可能觉得我说了一堆废话,那我们继续:


微信图片_20220528184041.jpg面试官

                                      自来也



在某项目中有这样一个老代码:

/* str: 待分割字符串

* vec: 出参。存储分割后的字符串数组

* sep: 分隔符 */

void str2vec(const string& str, vector& vec, char sep);


使用这个函数,实现一个宏:STR2VEC,使得它有这种效果:

auto vec = STR2VEC(str, sep);


题意解析一下,就是我们经常看到类似的split或者string转vector的函数。都需要这样使用:


string str = "abc:xxxx:123";
vector<string> vec;
str2vec(str, vec, ':');


单就这个需求来说,不需要用宏之类的。定义一个重载函数,比较好转换。当然这是一个抽象后的问题,大概知道有时候有这样一类需求,不是像三目运算符那样可以一行求值,当成函数用。需要一些额外操作才能出值,这时候怎么变成宏函数呢?


既然2020年了,那么肯定有C++11,那便有lambda了。lambda很多人都有用。通常我们调用lambda需要先定义,再调用,比如:


  auto fun = [](const string& str, char sep) {
        vector<string> vec;
        str2vec(str, vec, sep);
        return vec;
    };
    auto v = fun("abc:xxxx:123", ':');


可是这个怎么结合宏函数,变成一行调用的形式呢?

今天来介绍一种『原地调用』的lambda写法。

上述两个语句其实可以合并:


 [](const string& str, char sep) {
        vector<string> vec;
        str2vec(str, vec, sep);
        return vec;
    }("abc:xxxx:123", ':');


那么实现这个宏函数就不难了。


#define STR2VEC(STR, SEP) [](const string& str, char sep) { \
        vector<string> vec;\
        str2vec(str, vec, sep);\
        return vec;\
    }(STR, SEP);


就可以完成这种调用了:


auto v = STR2VEC("abc:xxxx:123", ':');




微信图片_20220528184041.jpg面试官

                                       自来也


          不错,你提了lambda,也提到了lambda可以原地调用。其实当lambda无参数的时候,写起来会更简单。

嗯。没错,再写一个版本:


#define STR2VEC(STR, SEP) [&] { \
        vector<string> vec;\
        str2vec(STR, vec, SEP);\
        return vec;\
    }();


当无参的时候, [] 后面的参数声明可以直接省略了。是不是更简洁了。

lamba形式繁多,在无参的时候,可以省略参数列表。另外其实完整版的lamba是连返回值类型也要声明的。但在编译器能正确推导返回值类型的时候,通常我们省略了。看一个完全体的lambda表达式:


[] (int x, int y) -> int { int z = x + y; return z; }


微信图片_20220528184041.jpg面试官

                                           自来也


我们继续,有一个策略类型Strategy,封装了很多计算操作。

有一个全局的策略map。可以通过策略名,找到对应的策略指针来指针。

需要写一个lambda内部有一系列拼装策略名称的逻辑(可以...)。当能走strategy_map中找到的时候,返回对应的策略指针,可能存在找不到的场景。

看下我这个代码有什么问题:

// 声明

unordered_map> strategy_map;

...

// 某文件中的lambda

auto fun = [&] {

   //引用捕获了一些外部数据,用以拼装得到策略名称name

   // ... 获取name的逻辑

   auto it = strategy_map.find(name);

   if (it != strategy_map.end();)  {

       return it->second;

   } else {

       return nullptr;

   }

};

...

auto stg = fun();

if (stg != nullptr) {

   stg->run();

}


问题就是简化版的lambda,无返回值声明。然而这个lambda内部可能返回两种类型。一种是Strategy*,另外一种是nullptr_t,编译会失败。



微信图片_20220528184041.jpg面试官

                                    自来也


 那可以怎么修改呢?


我想到两种办法。第一种是加上返回值声明,但是要加上返回值声明,参数列表也要一起补全。


auto fun = [&]() -> Strategy*{
    //引用捕获了一些外部数据,用以拼装得到策略名称name
    // ... 获取name的逻辑
    auto it = strategy_map.find(name);
    if (it != strategy_map.end();)  {
        return it->second;
    } else {
        return nullptr;
    }
};


还有一种可以不加上参数声明和返回值声明。但是要修改一下策略map的设计,使之能够让编译器推导出类型。我觉得此处用智能指针可能更好。

// 声明
unordered_map<string, shared_ptr<Strategy>> strategy_map;
...
auto fun = [&] {
    //引用捕获了一些外部数据,用以拼装得到策略名称name
    // ... 获取name的逻辑
    auto it = strategy_map.find(name);
    if (it != strategy_map.end();)  {
        return it->second;
    } else {
        return shared_ptr<Strategy>(); 
    }
};


shared_ptr<>()直接构造出来的对象和nullptr做比较返回是相等的!

这点和make_shared<>()不同哦!


微信图片_20220528184041.jpg面试官

                                     自来也


好了,先到这吧,HR会在7天内告诉你结果。收到面试调查问卷,不代表面试已结束。

相关文章
|
4天前
|
存储 安全 编译器
【C++入门 四】学习C++内联函数 | auto关键字 | 基于范围的for循环(C++11) | 指针空值nullptr(C++11)
【C++入门 四】学习C++内联函数 | auto关键字 | 基于范围的for循环(C++11) | 指针空值nullptr(C++11)
|
19天前
|
C++ 容器
【编程技巧】 C++11智能指针
C++11引入了智能指针以自动管理内存,防止内存泄漏和悬挂指针: - `shared_ptr`:引用计数,多所有权,适用于多个对象共享资源。 - `unique_ptr`:独占所有权,更轻量级,适用于单一对象所有者。 - `weak_ptr`:弱引用,不增加引用计数,解决`shared_ptr`循环引用问题。 ## shared_ptr - 支持引用计数,所有者共同负责资源释放。 - 创建方式:空指针、new操作、拷贝构造/移动构造,以及自定义删除器。 - 提供`operator*`和`operator-&gt;`,以及`reset`、`swap`等方法。 ## unique_ptr
228 1
|
20天前
|
存储 安全 编译器
【C++航海王:追寻罗杰的编程之路】引用、内联、auto关键字、基于范围的for、指针空值nullptr
【C++航海王:追寻罗杰的编程之路】引用、内联、auto关键字、基于范围的for、指针空值nullptr
37 5
|
21天前
|
存储 Java C#
C++语言模板类对原生指针的封装与模拟
C++|智能指针的智能性和指针性:模板类对原生指针的封装与模拟
|
19天前
|
C++
C++职工管理系统(类继承、文件、指针操作、中文乱码解决)
C++职工管理系统(类继承、文件、指针操作、中文乱码解决)
20 0
C++职工管理系统(类继承、文件、指针操作、中文乱码解决)
|
21天前
|
设计模式 C++ 开发者
C++一分钟之-智能指针:unique_ptr与shared_ptr
【6月更文挑战第24天】C++智能指针`unique_ptr`和`shared_ptr`管理内存,防止泄漏。`unique_ptr`独占资源,离开作用域自动释放;`shared_ptr`通过引用计数共享所有权,最后一个副本销毁时释放资源。常见问题包括`unique_ptr`复制、`shared_ptr`循环引用和裸指针转换。避免这些问题需使用移动语义、`weak_ptr`和明智转换裸指针。示例展示了如何使用它们管理资源。正确使用能提升代码安全性和效率。
22 2
|
3天前
|
安全 编译器 程序员
【C++11】智能指针
【C++11】智能指针
4 0
|
5天前
|
Java API
Java面试题:说明Lambda表达式在Java中的应用,以及函数式接口的概念和作用。
Java面试题:说明Lambda表达式在Java中的应用,以及函数式接口的概念和作用。
11 0
|
13天前
|
存储 安全 程序员