C++ 面试常见问题(C++11)

C++ 面试常见问题(C++11)

nullptr 和 NULL的区别

面试高频指数:★★★★☆

在 C++11 之前,我们通常使用 NULL 来表示空指针。

然而,在 C++ 中,NULL 的定义实际上是一个整数值 0,而不是一个真正的指针类型。

在函数重载和模板编程中这可能会导致一些问题和歧义。

为了解决这个问题,C++11 引入了一个新的关键字 nullptr,用于表示空指针。

nullptr 是一种特殊类型的字面值,类型为 std::nullptr_t,定义为: typedef decltype(nullptr) nullptr_t,可以隐式转换为任何指针类型。

与 NULL 不同,nullptr 是一个真正的指针类型,因此可以避免一些由于 NULL 是整数类型而引起的问题。

以下是 nullptr 和 NULL 之间区别的一些例子

函数重载

#include <iostream>

void foo(int x) {
    std::cout << "foo() called with an int: " << x << std::endl;
}

void foo(char* x) {
    std::cout << "foo() called with a char*: " << x << std::endl;
}

int main() {
    // foo(NULL); // 编译错误:因为 NULL 会被解析为整数 0,导致二义性
    foo(nullptr); // 无歧义:调用 void foo(char* x)
}

函数模板

#include <iostream>
#include <type_traits>

template <typename T>
void bar(T x) {
    if (std::is_same<T, std::nullptr_t>::value) {
        std::cout << "bar() called with nullptr" << std::endl;
    } else {
        std::cout << "bar() called with a non-nullptr value" << std::endl;
    }
}

int main() {
    bar(NULL); // 输出:bar() called with a non-nullptr value,因为 NULL 被解析为整数 0
    bar(nullptr); // 输出:bar() called with nullptr
}

C++ 类型萃取

面试高频指数:★★☆☆☆

什么是 type_traits

在C++中,类型萃取(type_traits)是一种编译时技术,用于在编译期间获取和操作类型的信息。

主要用于泛型编程以及在编译时做出决策。

类型萃取可以帮我们检查和处理类型特性,从而优化代码、避免错误或提高性能。

C++11 引入了 <type_traits> 头文件,其中包含许多内置的类型萃取。下面是一些常见的例子:

  1. std::is_integral:判断类型 T 是否为整数类型。

  2. std::is_floating_point:判断类型 T 是否为浮点数类型。

  3. std::is_pointer:判断类型 T 是否为指针类型。

  4. std::is_reference:判断类型 T 是否为引用类型。

  5. std::is_const:判断类型 T 是否为 const 类型。

  6. std::is_same<T, U>:判断类型 T 和 U 是否相同。

这些类型萃取通常具有一个静态布尔值 value,当类型符合特定条件时,它为 true,否则为 false。

其实这些萃取函数原理都差不多,这里举一个例子说明下,比如 is_integral 用于判断一个变量是否是整形:

#include <type_traits>

template <typename T>
struct is_integral_helper : std::false_type {};

template <>
struct is_integral_helper<bool> : std::true_type {};

template <>
struct is_integral_helper<char> : std::true_type {};

template <>
struct is_integral_helper<short> : std::true_type {};

template <>
struct is_integral_helper<unsigned short> : std::true_type {};

template <>
struct is_integral_helper<int> : std::true_type {};

..... 依次类推各种整形都定义一个特化版本

template <typename T>
struct is_integral : is_integral_helper<typename std::remove_cv<T>::type> {};

在这个实现中,我们首先定义一个辅助模板 is_integral_helper,它默认继承自 std::false_type。

然后,我们为所有标准整数类型(包括有符号、无符号和字符类型)提供特殊化,使它们继承自 std::true_type。

( 核心思想就是为所有的整形提供一个特殊版本,其它非整形的就只能匹配到默认的版本,也就是 false_type

最后,我们定义 is_integral 作为 is_integral_helper 的一个包装,它首先移除给定类型的 const 和 volatile 限定符,然后应用 is_integral_helper。

std::true_type 和 std::false_type 是两个辅助类,分别用于表示编译时的 true 和 false 值。这两个类都有一个名为 value 的静态常量数据成员,它们的值分别是 true 和 false。

实际的实现可能更复杂,以适应各种编译器和平台的特性。

萃取如何用

举一个例子,如何使用类型萃取来选择不同的函数实现:

#include <iostream>
#include <type_traits>

template <typename T>
typename std::enable_if<std::is_integral<T>::value, T>::type
foo(T t) {
    std::cout << "foo() called with an integral type: " << t << std::endl;
    return t;
}

template <typename T>
typename std::enable_if<std::is_floating_point<T>::value, T>::type
foo(T t) {
    std::cout << "foo() called with a floating point type: " << t << std::endl;
    return t;
}

int main() {
    foo(42); // Output: foo() called with an integral type: 42
    foo(3.14); // Output: foo() called with a floating point type: 3.14
}

上面代码中使用 std::enable_if 和类型萃取来选择不同的函数实现。当类型为整数时,将调用第一个 foo() 函数;当类型为浮点数时,将调用第二个 foo() 函数。

当然,上面的例子你会说那我可以用 foo 函数重载 int 和float类型作为参数啊,不一定非要用萃取。

那么这里再举一个例子:

假设我们有一个模板函数 print(),用于打印容器中的元素。我们希望这个函数对于有 const_iterator 类型的容器使用 const_iterator,而对于没有 const_iterator 的容器使用普通的 iterator。

#include <iostream>
#include <vector>
#include <type_traits>

template <typename T>
struct has_const_iterator {
private:
    typedef char yes[1];
    typedef char no[2];

    template <typename C>
    static yes& test(typename C::const_iterator*);
    template <typename C>
    static no& test(...);
public:
    static const bool value = sizeof(test<T>(nullptr)) == sizeof(yes);
};

template <typename Container>
typename std::enable_if<has_const_iterator<Container>::value>::type
print(const Container& c) {
    typename Container::const_iterator it;
    std::cout << "Using const_iterator." << std::endl;
    for (it = c.begin(); it != c.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;
}

template <typename Container>
typename std::enable_if<!has_const_iterator<Container>::value>::type
print(const Container& c) {
    typename Container::iterator it;
    std::cout << "Using iterator." << std::endl;
    for (it = c.begin(); it != c.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;
}

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5};
    print(v);
}

上面代码使用类型萃取 has_const_iterator 来检查容器是否具有 const_iterator 类型。

然后,使用 std::enable_if 选择合适的 print() 函数实现。这样对于有 const_iterator 的容器,可以优先使用 const_iterator 进行遍历。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一些常见的C++面试问题及其答案: 1. 什么是C++C++是一种面向对象的编程语言,用于开发高性能的应用程序,包括操作系统、游戏、数据库、Web浏览器、图形用户界面等。 2. C++中的继承是什么? 继承是一种面向对象的编程概念,它允许一个类继承另一个类的属性和方法。子类继承父类的所有公有成员,包括变量和函数。继承使得代码可重用性更高,同时也使代码更易于维护。 3. C++中的多态是什么? 多态是一种面向对象的编程概念,它允许不同的对象对同一消息作出不同的响应。在C++中,多态是通过继承和虚函数实现的。 4. C++中的虚函数是什么? 虚函数是一种特殊的成员函数,允许派生类重写基类的函数。当使用指向基类的指针或引用调用虚函数时,将调用派生类的版本。虚函数允许实现多态性。 5. C++中的引用是什么? 引用是一种指向变量的别名,它与指针不同,不需要使用解引用运算符。引用通常用于函数参数,允许修改函数调用中的变量。 6. C++中的const关键字是什么? const关键字用于定义常量,即不能更改的变量。const还可以用于函数参数和函数返回值,以指示它们不能被修改。const还可以用于类成员函数,以指示它们不会修改对象的状态。 7. C++中的模板是什么? 模板是一种通用编程技术,允许编写不依赖于特定数据类型的代码。模板可以用于函数和类,允许在编译时生成代码。 8. C++中的STL是什么? STL是标准模板库的缩写,是C++标准库的一部分。STL提供了一组通用的数据结构和算法,包括向量、列表、堆栈、队列、映射、集合、排序和搜索等。 9. C++中的内存管理是什么? C++中的内存管理是指程序在运行时如何使用和释放内存。C++中的内存由程序员手动分配和释放,使用new和delete运算符。C++还提供了一组智能指针类,帮助程序员管理内存。 10. C++中的异常处理是什么? 异常处理是一种处理程序运行时错误的方法,允许程序在出现错误时终止执行,并提供错误处理机制。C++中的异常处理使用try、catch和throw关键字实现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值