關於面試,那些我完全不知道的知識

spacelan發表於2019-05-10

這裡記錄一下在找工作前的複習過程中那些讓我懷疑我是不是真的學過計算機的知識點。。


C/C++

cout的格式化輸出

cout << boolalpha << (1 == 2);

輸出false
參考連結:http://www.cnblogs.com/devymex/archive/2010/09/06/1818754.html

const與指標

const*的左側,如 const int *aint const *a,不能改變指標a指向的內容的值
const*的右側,如 int* const a,指標a本身為常量,不能改變a的指向

個人覺得把const放在型別符號後邊意義更明確一些。。

mutable與const

在類中,修飾為mutable的成員變數可以被修飾為const的成員函式修改

class A {
 public:
  mutable int a;
  void set_a(int n) const {
    a = n;
  }
};

sizeof括號內的祕密

sizeof()結果由編譯時確定,()內程式碼不被執行

int a = 0;
sizeof(a = 1);

a的值不會被改變

指標和引用的區別與聯絡

注意,引用也是要佔用記憶體空間的,內容是被引用的變數的地址,同指標一樣

struct st1
{
	int a;
	int &b;
	st1(int n) : a(n), b(a) {}
};

struct st2
{
	int a;
	int *b;
	st2(int n) : a(n), b(&a) {}
};

st1 s1(10);
st2 *ps2 = reinterpret_cast<st2*>(&s1);
cout << *(ps2->b); // 10

參考連結:http://blog.csdn.net/thisispan/article/details/7456169

指標陣列,還是陣列指標,還是函式指標陣列,還是。。

int *a[10];//包含十個元素的陣列,陣列元素是整型指標
int *(a[10]);//同上
int (*a)[10];//指向具有十個整型元素的陣列的指標
int (*a)();//指向無引數且返回整型的函式的指標
int (*a[10])();//包含十個元素的陣列,陣列元素是指向無引數且返回整型的函式的指標
int (**a)[10];//指向一維陣列指標的指標,陣列具有十個整型元素
int *(*a)[10];//指向一維陣列的指標,陣列具有十個整型指標元素
int (*(*a)(int))();//指向具有一個整型引數且返回函式指標的函式的指標,返回的函式指標指向無引數且返回整型的函式

陣列名取址?

int a[5] = {1, 2, 3, 4, 5};
int *p = (int*)(&a + 1);
cout << (int)*(p - 1);

輸出為5
陣列名a是指向陣列第一個元素的指標,&a是指向陣列的指標,雖然兩個指向同一個地址,但是&a + 1實際上是將指標向後移動了sizeof(a),也就是20個位元組,與a + 1移動一個位元組不同
在這裡p類似於二維陣列

this指標從哪來

this指標實際上是類成員函式的第一個引數,由編譯器自動新增,通過暫存器傳遞,整個生命週期同函式引數一致

容器與迭代器

刪除vector等連續空間的容器元素後,相應迭代器會失效

struct與class

structclass除了預設訪問控制不同以外,其他完全相同。
struct預設訪問控制是publicclass預設訪問控制是private

建構函式初始化列表

除開父類建構函式,成員變數初始化按照成員變數在類中申明的順序執行,與初始化列表順序無關

類與行內函數

除了虛擬函式和靜態成員函式,其他函式可以是內聯的,包括建構函式和解構函式。
事實上,如果成員函式在類申明中定義,那麼預設是內聯的。類申明外定義的函式可以用inline修飾為內聯的。但需要注意的是,行內函數是內部連結的,只能在本檔案內被呼叫。

//A.h
class A {
 public:
  A() { cout << "A()" << endl; }
  inline virtual ~A() { cout << "~A()" << endl; }
  inline void func1() { cout << "func1" << endl; }
  void func2();
};

//A.cc
inline void A::func2() { cout << "func2" << endl; }

//main.cc
A *a = new A;
a->func1();
a->func2();// 編譯失敗
delete a;

看起來好像複製建構函式

class A {
 public:
  int m;
  A(int n) : m(n) {}
  A(float n) : m(n * 2) {}
};

A a = 10;  // a.m == 10
A a = 10.0;// a.m == 20

單個引數的函式如果不新增explicit,會發生隱式的型別轉換

私有繼承與保護繼承

私有繼承與保護繼承的派生類不能稱為子類,因為派生類並不能完成父類能做的所有事情

虛繼承與多重繼承

參考連結:http://www.oschina.net/translate/cpp-virtual-inheritance

類申明的幾個技巧

如何讓一個類不能被例項化?

將建構函式申明為private或純虛類(抽象類)

如何讓一個類只能在堆上例項化?

將解構函式申明為private

如何讓一個類只能在棧上例項化?

過載new運算子為private

如何讓一個類的物件不能被複制?

將複製建構函式和賦值函式宣告為private,或者宣告為 = delete

如何讓一個類不能被繼承?

加final關鍵字
其他方法:http://blog.chinaunix.net/uid-26983585-id-4373697.html

四種cast

static_cast

static_cast和C語言裡的強制型別轉換相似,不過編譯時會做型別檢查,只有相關的型別才能轉換,否則編譯錯誤

dynamic_cast

dynamic_cast在程式執行時根據物件的虛表進行向上或者向下轉換。轉換指標失敗時,返回nullptr,轉換引用失敗時,丟擲異常
然而Google並不建議使用dynamic_cast,參考:http://www.zhihu.com/question/22445339

const_cast

const_cast去除物件的只讀屬性

reinterpret_cast

reinterpret_cast不做二進位制轉換,僅僅是重新解釋二進位制塊,常常用在偏底層的操作上,可移植性稍弱

成員函式與物件

class A {
 public:
  static int m;
  int n;
  void func1() { cout << this << ` ` << m << endl; }
  void func2() { cout << this << ` ` << n << endl; }
  static void func3() { cout << m << endl; }
};
int A::m = 10;

A *a = 0;
a->func1();// 0 10
a->func2();// crash
a->func3();// 10
A::func3();// 10

如果物件指標指向錯誤的地址,物件成員函式仍然可以被呼叫,但是不能訪問非靜態成員變數。這裡同時也將靜態成員函式拿出來比較。
想一想,假如成員函式是虛擬函式,結果還會一樣麼?

位域

struct A {
  int a:1;
  int b:2;
  int c:3;
};

cout << sizeof(A);// 4

參考連結:http://www.cnblogs.com/bigrabbit/archive/2012/09/20/2695543.html

static的三個域

  • 模組

  • 函式

起到的作用有什麼異同

volatile

volatile關鍵字告訴編譯器不要優化被它修飾的變數,這個變數可能在編譯器控制範圍外發生改變,比如中斷或者其他執行緒
注意下面這個例子

int func(volatile int *p) {
  // return (*p) * (*p); // wrong
  int temp = *p;
  return temp * temp;
}

變數提升

不同型別的變數參與混合運算時,所有變數將會轉換為同一型別,為了不丟失精度,總是轉換為長度更長的變數
在32位機器上,char,short總是轉換為int,float總是轉換為double
一般情況下,不用關注轉換的問題,但是考慮下面一個例子:

signed char a = 0xe0;
unsigned int b = a;
cout << int(a) << ` ` << b;

結果 -32 4294967264
因為在a賦值給b時,a要提升為32位的unsigned int型。然而a是負數,在提升長度時高位將用1來補全,於是a被提升為0xffffffe0。賦值後,b的值為0xffffffe0,所以作為無符號整型輸出為4294967264

const成員變數初始化

const成員變數必須在建構函式初始化列表中被初始化。

class A {
 public:
  const int m;
  A(int n) : m(n) {}
};

static const成員變數可以在宣告時初始化

class A {
 public:
  static const int m = 0;
  A(int n) {}
};

static成員變數不能在宣告時初始化,也不能在建構函式初始化列表中初始化,只能在類外申明並初始化

C++ 11已經支援非靜態(static const例外)成員變數宣告時初始化,自定義型別不能執行帶引數的建構函式

函式呼叫方式

C和C++程式中,函式的呼叫有多種方式,可以通過在函式宣告時加上__stdcall__cdecl__fastcall來顯式指定
不同的函式呼叫方式體現在引數傳遞方式,編譯後函式命名方式,函式返回時棧清理方式的不同。
參考連結:http://www.cnblogs.com/zhangchaoyang/articles/2725189.html

變長引數

簡單的說,具有變長引數的函式在呼叫時(__cdecl方式),編譯器按正常的方式將引數從右到左入棧,函式內部再通過指標根據引數型別依次從棧中將引數取出

printf("%c,%d", `A`, 10);舉慄
因為引數從右到左進棧且棧是從高地址到低地址生長的,所以此時記憶體是這樣的:

低地址<<|    4 byte   | 1 byte | 4 byte |>>高地址
        |"%c,%d"的地址|  `A`   |   10   |

首先printf函式的第一個引數不是變長引數,這裡我們假設這個引數叫str,它是指向"%c,%d"的指標。有了str,我們自然就知道了變長引數的起始地址是&str+1,然後再根據"%c,%d",我們知道了變長引數有兩個而且型別分別為charint,於是我們就可以依次得到這兩個變數辣
參考連結:http://www.cnblogs.com/chinazhangjie/archive/2012/08/18/2645475.html

改變const變數的值

通過const_cast可以去除read-only屬性,但是編譯時編譯器仍將使用到const變數的地方直接替換為初始化的那個值,類似於巨集

int const a = 1;
int *p = const_cast<int*>(&a);
*p = 2;
cout << a << " " << *p; // 輸出: 1 2

如何重寫基類虛擬函式

override,貌似又是C++ 11的關鍵字

class A {
 public:
  virtual void func(int n);
}

class B : A {
 public:
  virtual void func(int n); // 不要這樣,如果引數寫錯了,相當於宣告瞭過載函式
  void func(int n); // 同上
  void func(int n) override; // 強制重寫基類函式,如果引數或返回值與基類虛擬函式不一致,編譯器報錯
}

未完……

相關文章