C++面試題記錄

昨天線上面試了一個C++的崗位,這裏稍微記錄一下被問到的問題。
面完之後反思自己答得不是很好,緊張是一方面(寫代碼的時候共享屏幕更是有種被視奸的感覺= =),另一方面確實準備的也不夠充分。
機試第一題我又自己重新思考了一遍,重新寫了份答案,編譯是通過的,分享出來,如有問題,希望各位看客可以指點一二,感激。

參考鏈接:微軟文檔:移動構造函數和移動賦值運算符 (C++)
CSDN 面向對象思考:C++11新特性(50)- 移動構造函數和移動賦值

  • 介紹一下STL的容器類

STL容器庫,包含vector、map、list、set等類型,C++11引入unordered_map、unordered_set、array。

  • 左值引用和右值引用的區別

左值引用 T &
常規引用,一般表示對象的身份。

右值引用 T &&
右值引用就是必須綁定到右值(一個臨時對象、將要銷燬的對象)的引用,一般表示對象的值。
它的引入解決了 C++ 中大量的歷史遺留問題,消除了諸如 std::vector、std::string 之類的額外開銷,也才使得函數對象容器 std::function 成爲了可能。
右值引用可實現轉移語義(Move Sementics)和精確傳遞(Perfect Forwarding),它的主要目的有兩個方面:
消除兩個對象交互時不必要的對象拷貝,節省運算存儲資源,提高效率。
能夠更簡潔明確地定義泛型函數。

move 語義
傳統的 C++ 沒有區分『移動』和『拷貝』的概念,造成了大量的數據移動,浪費時間和空間。右值引用的出現恰好就解決了這兩個概念的混淆問題
標準庫中的std::move()
將使用 push_back(const T&&), 不會出現拷貝行爲
而整個字符串會被移動到 vector 中,所以有時候 std::move 會用來減少拷貝出現的開銷
這步操作後, str 中的值會變爲空

#include <utility>  // std::move
std::string str = "Hello world.";
std::vector<std::string> v;
v.push_back(std::move(str));
  • 機試題1:寫一個vect類,實現拷貝構造函數和移動構造函數。

面試官要求是寫一個類似於vector的類,實現拷貝構造和移動構造,考察的是對右值引用的理解。遺憾的是我當場沒有寫出來移動構造,拷貝構造的函數參數也忘記了用左值引用,果然背概念是沒有用的,我光記得個move能實現移動語義Orz…

以下代碼在coliru在線編譯器下測試,時間性能:
不用移動構造:用移動構造: 9.1s VS 4.6s)
編譯環境GCC 9.2.0
編譯語句g++ -std=c++17 -O2 -Wall -pedantic -pthread main.cpp && ./a.out

#include <iostream>
#include <string>
#include <vector>
#include <string.h>
#include <chrono>

using namespace std;
using namespace std::chrono;

template<typename T>
std::ostream& operator<<(std::ostream& os, const std::vector<T>& vec)
{
    for (auto& el : vec)
    {
        os << el << ' ';
    }
    return os;
}


const int defaultLength = 8; //默認的初始容量

class Vect {
private:
    int length;
    int *data;
public:
    // 默認構造
    Vect()
    :length(defaultLength)
    ,data(new int[defaultLength])
    {}

    Vect(int leng)
    :length(leng)
    ,data(new int[leng])
    {}

    Vect(int* d, int len)
    {
        length = len;
        data = new int[length];
        memcpy(data, d, length * sizeof(int));
    }

    // 析構函數
    ~Vect(){
        if(data != nullptr){
            delete [] data;
            data = nullptr;
            length = 0 ;
        }
    }
    // 拷貝構造
    Vect(Vect& input) {
        length = input.size();
        data = new int[length];
        memcpy(data, input.data, length * sizeof(int));
    }
    // 賦值
    Vect & operator=(const Vect& input){
        if(this != &input){
            delete [] data;
            length = input.length;
            data = input.data;
        }
        return *this;
    }
    // 移動賦值
    Vect & operator=(Vect&& input){
        if(this != &input){
            delete [] data;
            length = input.length;
            data = input.data;
            input.data = nullptr;
            input.length = 0;
        }
        return *this;
    }
    // 移動構造 
    Vect(Vect && input)
    :length(0)
    ,data(nullptr)
    {
        if(input.data != nullptr){
            data = input.data;
            length = input.length;
            input.data = nullptr;
            input.length = 0;
        }
    }
    // 移動構造
    // 另一種寫法,直接調用移動賦值構造函數,在已經有了移動賦值構造函數的前提下,可以減少代碼冗餘
    // Vect(Vect && input)
    // :length(0)
    // ,data(nullptr) {
    //     *this = std::move(input);
    // }
    int size(){
        return length;
    }

};

//#define STDMOVE

int main()
{
    std::vector<std::string> vec = {
        "Hello", "from", "GCC", __VERSION__, "!" 
    };
    std::cout << vec << std::endl;
    
        //測試性能
    auto start = system_clock::now();
    int hello[] = {1,1,2,2,3,4};
    Vect v1(hello, sizeof(hello)/sizeof(hello[0]));//數組單元個數
    for(int i = 0; i < 100000000; i++){

#ifndef STDMOVE
        Vect v2(v1); //拷貝構造
        Vect v3 = v2; //賦值構造
#else
        Vect v2(std::move(v1)); //移動構造
        Vect v3;
        v3 = std::move(v2); //移動賦值構造
#endif

    }
    auto end = system_clock::now();
    std::chrono::duration<double> diff = end-start;
    cout <<  diff.count() << " s\n" << endl;
   
    return 0;
    
}

  • 機試題2:實現一個浮點數開根號的函數

思路就是把0-1的數和1以上的數分開討論。
大於等於1的數,初始範圍是[1,i),用二分法去不斷縮小範圍
同理,大於等於0小於1的數,初始範圍[0,1)
下面只寫了大於1的情況。還要記得判斷小於0的話需要返回錯誤信息。

float sqartfloat(float input){
    if(input < 0) {
        thorw std::invalid_argument;
    };
    if(input >= 1) {
        float j = 1;
        float out = 0;
        for(float i = input / 2; i >= j; ){
            out = i * i;
            if(fabs(out - input) < 0.01){
                return i;
            }
            if (out > input) {
                i = i / 2;
            } else {
                i = j;
                j = i / 2;
            }
        }
    }
    if (input < 1 && input >= 0){
        //...
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章