c++面试一

1.#include使用

 在C/C++中,#include 预处理指令用于包含头文件,这些头文件通常包含了函数声明、宏定义以及其他的声明和定义。#include 指令后面跟着的文件名可以使用双引号 "" 或尖括号 <> 来指定,它们之间有一些区别。

  1. 双引号 ""
    • 当文件名被双引号 "" 括起来时,预处理器会首先在当前源文件所在目录下查找要包含的文件。
    • 如果在当前目录下找不到指定的文件,预处理器会继续在系统默认的头文件目录中查找(通常是编译器的包含目录)。
  2. 尖括号 <>
    • 当文件名被尖括号 <> 括起来时,预处理器会直接在系统默认的头文件目录中查找要包含的文件。
    • 它通常用于包含系统提供的标准库头文件或者其他全局可用的头文件。

2. 常量指针和指针常量的区别

        2.1 const的优点

        在C/C++中,宏定义使用 #define 来定义一个符号常量或者带参数的代码替换。宏定义在预处理阶段被简单地进行文本替换,它不会进行任何类型检查。这意味着在宏定义中,不会进行任何类型检查,而且宏定义的参数也不会进行类型检查。

2.2 常量指针和指针常量的区别

const修饰的变量不能作为左值,初始化完不能被修改。
const的编译方式不同,在c中const就是当作变量来编译生成指令的,c++中,所有出现const常量名字的地方,都被常量的初始化替换。

在c语言中,const修饰的量可以不用初始化(但是没啥意义) ,但也不是常量,叫做常变量 

在c++中,,const修饰的量必须初始化,叫做常量

C++/C中const的区别 - 不同初始化方式对C++中const量性质的影响_c const没有初始化-CSDN博客

3.  c++11的新特性

3.1 初始化列表
  • 初始化列表允许使用 {} 语法来初始化数组、容器和类对象。
  • 它提供了一种更简洁、一致的初始化方式,并且可以防止窄化转换。

3.2 auto关键字

3.3 decltype关键字

3.4 范围for循环

3.5 nullptr关键字

void fun(int x) {
    cout << x << endl;
}

void fun(int* p) {
    if (p != nullptr)
        cout << *p << endl;
}

int main() {
    fun(0);  // 在 C++98 中编译可能失败,存在二义性,但在 C++11 中编译为 fun(int)
    int* p = nullptr;  // 使用 nullptr 初始化空指针
    return 0;
}
3.6 lambda表达式

Lambda表达式是一种匿名函数,它允许我们在需要函数作为参数的地方以内联的方式定义函数。

3.7 智能指针

3.8 右值引用

        右值引用是C++11引入的特性,用于标识对临时对象(右值)的引用。它们通过 && 符号来声明,与左值引用 & 相对应。

        右值引用的主要目的是提高C++中的移动语义(Move Semantics),允许在不进行资源拷贝的情况下将临时对象的内容转移给另一个对象,从而提高性能和效率。

        右值引用通常与移动构造函数和移动赋值运算符一起使用,这些特殊的成员函数允许在对象之间转移资源而不是复制资源。这对于管理动态分配的内存和其他资源非常有用,因为它减少了不必要的内存分配和释放。

3.9 移动语义 以及 移动构造函数和移动赋值运算符

        移动语义是C++11引入的特性,它允许在不进行资源复制的情况下将资源从一个对象转移到另一个对象。这样做可以避免不必要的内存分配和释放,提高程序的性能和效率。移动语义通常与右值引用一起使用,用于转移临时对象的资源。

        移动构造函数和移动赋值运算符与传统的复制构造函数和赋值运算符类似,但是接受的参数是右值引用(&&),表示它们可以接受临时对象或将要销毁的对象。在移动语义中,资源的所有权从右值对象转移到目标对象,右值对象的状态通常被置为有效但不再拥有资源的状态,这样可以避免重复释放资源。

        在 C++ 中,std::move 并不是将左值直接转换为右值引用,而是将左值强制转换为右值。这是因为右值引用绑定到右值,而左值引用绑定到左值。当我们使用 std::move 将一个左值传递给一个函数时,我们实际上是告诉编译器:不要再将该左值视为一个左值,而是视为一个可以移动的右值,这样在特定的情况下可以调用移动构造函数或移动赋值运算符,以提高性能。

        但是,std::move 并不会将左值变成右值引用,因为这样的转换是不安全的。右值引用表示一个临时对象,它的生命周期通常很短,而左值是一个具有持久性的对象。如果我们将一个左值直接转换为右值引用,那么就会让这个左值变成了临时对象,这可能导致程序中的错误行为或潜在的安全问题。

  std::move 仅适用于左值,它并不会改变参数的类型,只是将其视为右值引用,从而触发移动构造函数或移动赋值运算符。

        移动构造函数和移动赋值运算符是类的特殊成员函数,它们用于实现移动语义。移动构造函数允许将资源从一个对象移动到另一个对象,而不是进行深度复制。移动赋值运算符执行类似的操作,允许在赋值操作中转移资源。这两个特殊成员函数通常使用右值引用参数来实现。

#include <iostream>
#include <vector>

class MyVector {
public:
    int size;
    int* data;

    // 构造函数
    MyVector(int s) : size(s), data(new int[s]) {
        std::cout << "Constructing MyVector of size " << size << std::endl;
    }

    // 移动构造函数
    MyVector(MyVector&& other) noexcept : size(other.size), data(other.data) {
        other.size = 0;
        other.data = nullptr;
        std::cout << "Moving resources from one MyVector to another" << std::endl;
    }

    // 析构函数
    ~MyVector() {
        delete[] data;
        std::cout << "Destroying MyVector" << std::endl;
    }
};

int main() {
    // 创建一个临时的MyVector对象
    MyVector temp(5);
    
    // 创建一个新的MyVector对象,并使用移动构造函数将资源从temp移动到vec
    MyVector vec(std::move(temp));

    // 这时temp的资源已经被移动到vec,temp不再拥有资源,其析构函数会被调用
    std::cout << "Size of temp: " << temp.size << std::endl; // 应该输出0

    // vec现在拥有temp之前的资源
    std::cout << "Size of vec: " << vec.size << std::endl; // 应该输出5

    return 0;
}
3.10引用折叠

        引用折叠是 C++ 中涉及引用的一种特殊规则,它是在模板类型推导、函数重载和类型别名中引入的。引用折叠的规则如下:

  1. 当一个模板参数被推导为一个引用类型时,引用折叠会发生。

  2. 如果一个模板参数被推导为一个左值引用类型(例如 T&),并且被另一个左值引用类型引用(例如 T&T&&),或者被右值引用类型引用(例如 T&&),那么两个引用会折叠成一个左值引用。

  3. 如果一个模板参数被推导为一个右值引用类型(例如 T&&),那么它不会和其他引用类型折叠,它仍然是一个右值引用。

        引用折叠的主要作用是在模板参数推导和函数重载中简化语言规范,同时保留引用类型的重要语义。它通常与移动语义和完美转发等技术结合使用,以提高代码的通用性和灵活性。

template<typename T>
void foo(T&& x) {
    // 在这里,T&& 可能是左值引用或右值引用,取决于传入参数的类型
}

int main() {
    int a = 5;
    foo(a); // T 被推导为 int&,引用折叠后为 int&
    foo(10); // T 被推导为 int,引用折叠后为 int&&
    return 0;
}
 3.11 完美转发

        完美转发是 C++11 引入的一种语言特性,用于在函数模板中保留参数的值类别(左值或右值),并将其传递给另一个函数,同时保留参数的引用性质(左值引用或右值引用)。

        在 C++ 中,通常情况下,函数参数传递可以分为值传递和引用传递两种方式。值传递会创建参数的副本,而引用传递会直接操作原始参数。然而,在某些情况下,我们希望在函数模板中将参数按照原始类型和引用性质传递给另一个函数,而不改变其类型或引用性质。

        完美转发允许我们在函数模板中按照参数的原始类型和引用性质将参数传递给另一个函数,而不会丢失信息或改变参数的类型。通常情况下,我们使用 std::forward 函数来实现完美转发。std::forward 是一个模板函数,它接受一个参数并将其按照其原始类型和引用性质转发给另一个函数。

#include <iostream>
#include <utility> // 包含 std::forward

// 目标函数,接受左值引用参数
void process(int& x) {
    std::cout << "Processing lvalue: " << x << std::endl;
}

// 目标函数,接受右值引用参数
void process(int&& x) {
    std::cout << "Processing rvalue: " << x << std::endl;
}

// 模板函数,实现完美转发
template<typename T>
void forwarder(T&& x) {
    process(std::forward<T>(x));
}

int main() {
    int a = 5;
    forwarder(a); // 调用 process(int&),传递左值引用参数
    forwarder(10); // 调用 process(int&&),传递右值引用参数
    return 0;
}

  • 12
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值