C/C++面試知識點總結(一)
目錄:
一、基礎知識
1.C/C++
一、基礎知識
1.C/C++
(1).struct大小的確定
由於記憶體對齊的原則,在32位機器上,記憶體是4位元組對齊,也就是說,不夠4個位元組的按 4位元組來算。同理,在32位機器上,記憶體是8位元組對齊。
例子:
struct test
{
int a;
char b;
int c;
}TEST;
其大小為4+4+4 = 12b
struct test
{
int a;
char b;
char d;
char e;
char f;
int c;
}TEST;
其大小為4+(1+1+1+1)+4 = 12b
struct test
{
int a;
char b;
int c;
char d;
}TEST;
其大小為4+4+4+4 = 16b
struct test
{
char a;
}TEST;
其大小為1b
struct test
{
char a;
char b;
}TEST;
其大小為2b,說明如果前面的位元組+後面的位元組不超過記憶體對齊所需要的位元組,是不會用0填充的。實際多少個位元組就是多少個位元組。
(2).strlen、strcpy的實現
int _strlen(const char* str)
{
if (!str || *str == 0) return 0;
int len = 0;
while (*str++)++len;
return len;
}
char* _strcpy(char* d, const char* s)
{
if (!s || *s == 0) return d ? &(*d = 0) : NULL; //防止d未初始化亂碼
char* td = d;
if (d) while (*d++ = *s++); //防止d為NULL
return td;
}
(3).memcpy的實現以及如何防止記憶體重疊造成的錯誤。
我沒用過未定義的memcpy,我在VC、VS2013、VS2015測試的時候,memcpy已經對記憶體重疊進行檢測了。所以,這裡就寫一個未檢測記憶體重疊的和檢測記憶體重疊的memcpy。
void* Memcpy(void* des, const void* src, size_t count)
{
if (!src || count <= 0) return des;
char* td = (char*)des;
const char* ts = (const char*) src;
while (count--) *td++ = *ts++;
return des;
}
例子如下:
char* p = new char[6];
memcpy(p, "12345", 10);
printf("%s\n", p);
Memcpy(p + 1, p, 4);
printf("%s\n", p);
void* Memmove(void* des,const void* src,size_t count)
{
if (!src || count <= 0) return des;
char* td = (char*)des;
const char* ts = (char*)src;
//如果源地址 + count 小於目的地址,說明,記憶體無重疊,進行正向拷貝
if (ts + count < td)
{
while (count--) *td++ = *ts++;
}
//否則有記憶體重疊,進行逆向拷貝
else
{
char* ttd = td + count - 1;
const char* tts = ts + count - 1;
while (count--) *ttd-- = *tts--;
}
return des;
}
例子如下:
char* p = new char[6];
memcpy(p, "12345", 10);
printf("%s\n", p);
Memmove(p + 1, p, 4);
printf("%s\n", p);
附上一張記憶體重疊示意圖:
(4).全域性變數、區域性變數、靜態變數的作用域以及存放的記憶體區域。
(5).static關鍵字的作用
a.在函式體內部定義變數,該變數從程式開始到結束只會分配一次記憶體,當再次進入該函式的時候,其值不變,仍為上次退出時的值
例子:
int f()
{
static int i = 0;
return ++i;
}
int main()
{
cout << f() << f() << f() << endl;
return 0;
}
結果是321,為什麼是321這也是一個點,下面會有解釋。
b.模組內的static定義的變數和函式不能被外部引用,唯一的辦法是通過一個間接變數或者函式來引用才行。
例子:
A.cpp
static char C = 'A';
char c = C;
static int F()
{
return 5;
}
int ff()
{
return F();
}
B.cpp
int main()
{
extern int ff();
extern char c;
cout << ff() << endl;
cout << c << endl;
return 0;
}
c.類中定義的static變數屬於整個類,即類成員變數,與物件無關,只會在執行的時候建立一次。
d.類中定義的static函式屬於整個類,及類成員函式,與物件無關,所以不能在靜態函式裡面使用this指標,這個是物件獨有的。
例子:
class TEST
{
public:
static int m_i;
static int f() { return m_i; } //靜態成員函式只能引用靜態成員變數
};
int TEST::m_i = 6;
int main()
{
cout << TEST::f() << endl;
return 0;
}
(6).const關鍵字的作用
a.定義普通變數的時候,只能初始化一次,以後不可再修改其值。
b.定義指標變數時,再型別前,則值不能改,再型別後,則其地址不能改,若兩個都有,則兩者都不能改。
例子:
int a = 2, b = 3;
const int* p1 = &a;
*p1 = b; //值不可以修改
p1 = &b; //地址可以修改
int* const p2 = &a;
*p2 = b; //值可以修改
p2 = &b; //地址不可以修改
c.在函式形參宣告中,使用const,可以防止傳進來的引數被修改。
d.在類中使用const定義的函式,在函式內部不能修改成員變數的值,但是可以修改傳進來的形參值,但是一般不這麼用。
(7).sizeof是運算子而不是函式,計算變數或者結構體大小的時候可以不加括號,但是計算型別一定要加,所以計算什麼都加就對了。
(8).陣列地址問題
例子:
int a[] = { 1,2,3,4,5 };
cout << "a[0]: " << &a[0] << endl;
cout << "a[4]: " << endl;
cout << "(a[0] + 1): " <<&a[0] + 1 << endl;
cout << "(a + 1): " << a + 1 << endl;
cout << "&a + 1: " << &a + 1 << endl;
可以看出來,a和&a的值一樣,概念不一樣,a表示陣列的首地址,而&a表示的是整個物件的首地址,所以當它+1的時候,就會跳出陣列的邊界。
(9).如何將浮點數分解為整數和小數部分
a.強轉為int即為整數部分,然後再減去整數部分即可。
double Modf(double x, int* y)
{
*y = (int)x;
return x - *y;
}
b.直接使用庫函式,double modf(double x,double* y);
(10).其它一樣,只改變一樣,不能使函式過載的是
返回型別。
(11).C的結構體和C++的結構體的區別
a.C結構體內部不允許有函式存在,C++允許
b.內部成員變數許可權不同,C的只能是public,C++的可以有三種。
c.C結構體不可以繼承,C++可以繼承結構體或者類
(12).淺拷貝和深拷貝的原理
其實這兩個概念很簡單,淺拷貝就是兩個物件共享一塊記憶體,其缺點就是當析構一個物件的時候,另一個物件也不存在了,如果再使用它就會發生錯誤。深拷貝就是完完全全的複製出一個物件,兩者在記憶體上無任何關係。
(13).不能過載的5個運算子
.(成員訪問運算子)
->(成員指標運算子)
::(作用域解析運算子)
?(條件運算子)
sizeof運算子
(14)常見的不能宣告為虛擬函式的有哪些?
普通函式(非成員函式)
靜態成員函式
內聯成員函式
建構函式
友元函式。
(15)C++的靜態多型和動態多型
所謂靜態多型就是執行前確定型別或者函式呼叫(也就是編譯後確定),其採用的是函式過載和泛型(例如,模板。
所謂動態多型就是執行後確定型別或者函式呼叫,其採用虛擬函式實現。
(16)C++虛擬函式的原理
虛擬函式由虛表管理,這張表存放著所有虛擬函式的地址,而類的例項物件維護著一個虛表指標,指向這個表。執行的時候,根據new的物件裡面的虛表指標,確定呼叫的函式。如下例子所示:
class A
{
public:
virtual void f1() { cout << "A...f1()" << endl; }
};
class B :public A
{
public:
void f1() { cout << "B...f1()" << endl; }
};
int main()
{
A* a = new A; //因為不是抽象類,所以可以例項化
B* b = new B; //直接用子類自己例項化
A* pB = new B; //用基類指標指向子類
return 0;
}
這就是為什麼能在執行時確定呼叫哪個函式的原因了,當new B返回B的物件後,裡面會有一張B類的虛表,然後根據B的虛表呼叫B的函式。
(17)C++虛擬函式佔用類的大小
因為只需要維護一個指向虛表的指標,所以大小為4或8個位元組
靜態成員和函式不計入sizeof中。
(18)new與malloc的區別
(19)C++中有哪幾種資料儲存區?
棧、堆、自由儲存區、全域性/靜態儲存區、常量儲存區
(20)什麼是棧溢位?哪些情況下比較容易出現棧溢位?
棧溢位泛指系統維護的棧溢位,因資料壓不下去了,導致溢位,此時程式會崩潰。
一般遞迴深度過大、建立普通陣列過大(就是區域性變數佔用的空間大於棧了就會溢位,new的是堆,不算)。
(21)“#include”後面跟引號與尖括號的區別?
引號編譯器會搜尋當前工作目錄下的標頭檔案,尖括號會搜尋安裝目錄下的標頭檔案。
(22)gcc和g++的區別
(23)類成員函式的過載、覆蓋和重寫區別
過載和普通的函式過載一樣。
覆蓋則基類的函式要加virtual (這就是多型的實現)
如果派生類的函式與基類的函式同名,但是引數不同。此時,不論有無virtual關鍵字,基類的函式將被隱藏,子類就重寫了這個函式
如果派生類的函式與基類的函式同名,並且引數也相同,但是基類函式沒有virtual關鍵字。此時,基類的函式被隱藏,子類就重寫了這個函式
(24)建構函式為什麼不能是虛擬函式
虛擬函式存在於虛表中,而虛表要靠虛表指標維護,而只有例項化後的物件才有虛表指標,而例項化物件就必須呼叫建構函式初始化物件,所以衝突了,結果就是建構函式不能是虛擬函式。
(25)printf(“%d,%d\n”,i++,i++),若i=0,則結果輸出什麼。
這裡有一個點就是,printf和cout輸出的時候都是從右至左讀取值,所以結果應該是1,0。