昨天線上面試了一個C++的崗位,這裏稍微記錄一下被問到的問題。
面完之後反思自己答得不是很好,緊張是一方面(寫代碼的時候共享屏幕更是有種被視奸的感覺= =),另一方面確實準備的也不夠充分。
機試第一題我又自己重新思考了一遍,重新寫了份答案,編譯是通過的,分享出來,如有問題,希望各位看客可以指點一二,感激。
參考鏈接:微軟文檔:移動構造函數和移動賦值運算符 (C++)
CSDN 面向對象思考:C++11新特性(50)- 移動構造函數和移動賦值
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));
面試官要求是寫一個類似於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;
}
思路就是把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){
//...
}
}