基礎語法
1.在main執?之前和之后執?的代碼可能是什么?
main函數執?之前,主要就是初始化系統相關資源:
- 設置棧指針,其中棧存放的局部變量、函數參數、函數調用的返回地址
- 初始化靜態 static 變量和 global 全局變量,即 .data 段的內容
- 將未初始化部分的全局變量賦初值:數值型 short , int , long 等為 0 , bool 為 FALSE ,指針為 NULL 等等,即 .bss 段的內容
- 全局對象初始化,在 main 之前調?構造函數,這是可能會執?前的?些代碼
- 將main函數的參數 argc , argv 等傳遞給 main 函數,然后才真正運? main 函數__attribute__((constructor))
main函數執?之后: - 全局對象的析構函數會在main函數之后執?;
- 可以? atexit 注冊?個函數,它會在main 之后執?;
- attribute((destructor)(是GCC/Clang 的擴展語法,用于標記一個函數在 main()結束后或程序退出時自動執行。)
2、結構體內存對?問題?
- 結構體內成員按照聲明順序存儲,第?個成員地址和整個結構體地址相同。
- 未特殊說明時,按結構體中size最?的成員對?(若有double成員,按8字節對?。)
注意:c++11以后引?兩個關鍵字 alignas與 alignof。其中 alignof 可以計算出類型的對??式, alignas 可以指定結構體的對??式。
3、指針和引?的區別
- 指針是?個變量,存儲的是?個地址,引?跟原來的變量實質上是同?個東?,是原變量的別名
- 指針可以有多級,引?只有?級
- 指針可以為空,引?不能為NULL且在定義時必須初始化
- 指針在初始化后可以改變指向,?引?在初始化之后不可再改變
- sizeof指針得到的是本指針的??, sizeof引?得到的是引?所指向變量的??
- 當把指針作為參數進?傳遞時,也是將實參的?個拷?傳遞給形參,兩者指向的地址相同,但不是同?個變量,在函數中改變這個變量的指向不影響實參,?引?卻可以。
- 引?本質是?個指針,同樣會占4字節內存;指針是具體變量,需要占?存儲空間(,具體情況還要具體分析)。
- 引?在聲明時必須初始化為另?變量,?旦出現必須為typename refname &varname形式;指針聲明和定義可以分開,可以先只聲明指針變量?不初始化,等?到時再指向具體變量。
- 引??旦初始化之后就不可以再改變(變量可以被引?為多次,但引?只能作為?個變量引?);指針變量可以重新指向別的變量。
- 不存在指向空值的引?,必須有具體實體;但是存在指向空值的指針。
void test(int *p)
{int a=1;p=&a;cout<<p<<" "<<*p<<endl;
}
int main(void)
{int *p=NULL;test(p);if(p==NULL)cout<<"指針p為NULL"<<endl;return 0;
}
//運?結果為:
//0x22ff44 1
//指針p為NULL
void testPTR(int* p) {int a = 12;p = &a;
}
void testREFF(int& p) {int a = 12;p = a;
}
void main()
{int a = 10;int* b = &a;testPTR(b);//改變指針指向,但是沒改變指針的所指的內容cout << a << endl;// 10cout << *b << endl;// 10a = 10;testREFF(a);cout << a << endl;//12
}
注意:在編譯器看來, int a = 10; int &b = a; 等價于 int * const b = &a; ? b = 20; 等價于 *b = 20; ?動轉換為指針和?動解引?.其中testREFF(a);編譯器會??自動將 x綁定到 ref??,相當于:int& p = a; // 隱式發生在函數調用時
4、在傳遞函數參數時,什么時候該使用指針,什么時候該使用引用呢?
- 需要返回函數內局部變量的內存的時候?指針。使?指針傳參需要開辟內存,?完要記得釋放指針,不然會內存泄漏。?返回局部變量的引?是沒有意義的
// 正確:返回堆內存的指針(調用者需手動釋放)
int* createArray(int size) {int* arr = new int[size]; // 堆內存,函數結束后仍存在for (int i = 0; i < size; i++) arr[i] = i;return arr; // 返回指針
}int main() {int* ptr = createArray(5);// 使用 ptr...delete[] ptr; // 必須手動釋放!return 0;
}
- 對棧空間???較敏感(?如遞歸)的時候使?引?。使?引?傳遞不需要創建臨時變量,開銷要更?
- 類對象作為參數傳遞的時候使?引?,這是C++類對象傳遞的標準?式
class BigObject {// 假設類內有大量數據...
};// 正確:傳遞常量引用(避免拷貝)
void printObject(const BigObject& obj) {// 只讀操作
}// 正確:傳遞引用(可修改對象)
void modifyObject(BigObject& obj) {// 修改 obj
}int main() {BigObject obj;printObject(obj); // 無拷貝modifyObject(obj); // 無拷貝return 0;
}
5、堆和棧的區別
棧空間默認是4M, 堆區?般是 1G - 4G
6、你覺得堆快?點還是棧快?點?
毫?疑問是棧快?點。
因為操作系統會在底層對棧提供?持,會分配專?的寄存器存放棧的地址,棧的?棧出棧操作也?分簡單,并且有
專?的指令執?,所以棧的效率?較?也?較快。
?堆的操作是由C/C++函數庫提供的,在分配堆內存的時候需要?定的算法尋找合適??的內存。并且獲取堆的內
容需要兩次訪問,第?次訪問指針,第?次根據指針保存的地址訪問內存,因此堆?較慢。
7、區別以下指針類型?
int *p[10]
int (*p)[10]
int *p(int)
int (*p)(int)
- int *p[10]表示指針數組,強調數組概念,是?個數組變量,數組??為10,數組內每個元素都是指向int類型的指針變量。
- int (*p)[10]表示數組指針,強調是指針,只有?個變量,是指針類型,不過指向的是?個int類型的數組,這個數組??是10。
- int *p(int)是函數聲明,函數名是p,參數是int類型的,返回值是int *類型的。
- int (*p)(int)是函數指針,強調是指針,該指針指向的函數具有int類型參數,并且返回值是int類型的。
8、new / delete 與 malloc / free的異同
相同點:
- 都可?于內存的動態申請和釋放
不同點: - 前者是C++運算符,后者是C/C++語?標準庫函數
- new?動計算要分配的空間??, malloc需要??計算
- new是類型安全的, malloc不是。例如:
int *p = new float[2]; //編譯錯誤
int *p = (int*)malloc(2 * sizeof(double));//編譯?錯誤
- new調?名為operator new的標準庫函數分配?夠空間并調?相關對象的構造函數, delete對指針所指對象運?適當的析構函數;然后通過調?名為operator delete的標準庫函數釋放該對象所?內存。后者free均沒有相關調?,可能會造成內存泄漏(如果對象內部有動態分配的資源(如 new int[100]),free不會調用析構函數)
- 后者需要庫?件?持,前者不?
- new是封裝了malloc,直接free不會報錯,但是這只是釋放內存,?不會析構對象
9、new和delete是如何實現的?
- new的實現過程是:?先調?名為operator new的標準庫函數,分配?夠?的原始為類型化的內存,以保存指定類型的?個對象;接下來運?該類型的?個構造函數,?指定初始化構造對象;最后返回指向新分配并構造后的的對象的指針
- delete的實現過程:對指針指向的對象運?適當的析構函數;然后通過調?名為operator delete的標準庫函數釋放該對象所?內存
10、malloc和new的區別?
- malloc和free是標準庫函數,?持覆蓋; new和delete是運算符,不重載。
- malloc僅僅分配內存空間, free僅僅回收空間,不具備調?構造函數和析構函數功能,?malloc分配空間存儲類的對象存在?險; new和delete除了分配回收功能外,還會調?構造函數和析構函數。
- malloc和free返回的是void類型指針(必須進?類型轉換), new和delete返回的是具體類型指針。
11、既然有了malloc/free, C++中為什么還需要new/delete呢?直接?malloc/free不好嗎
- malloc/free和new/delete都是?來申請內存和回收內存的。
在對?基本數據類型的對象使?的時候,對象創建的時候還需要執?構造函數,銷毀的時候要執?析構函數。 - ?malloc/free是庫函數,是已經編譯的代碼,所以不能把構造函數和析構函數的功能強加給malloc/free,所
以new/delete是必不可少的。
12、被free回收的內存是?即返還給操作系統嗎?
不是的,被free回收的內存會?先被ptmalloc使?雙鏈表保存起來,當?戶下?次申請內存的時候,會嘗試從這些內存中尋找合適的返回。這樣就避免了頻繁的系統調?,占?過多的系統資源。同時ptmalloc也會嘗試對?塊內存進?合并,避免過多的內存碎?。
13、宏定義和函數有何區別?
- 宏在編譯時完成替換,之后被替換的?本參與編譯,相當于直接插?了代碼,運?時不存在函數調?,執?起來更快;函數調?在運?時需要跳轉到具體調?函數。
*宏定義屬于在結構中插?代碼,沒有返回值;函數調?具有返回值。 - 宏定義參數沒有類型,不進?類型檢查;函數參數具有類型,需要檢查類型。
- 宏定義不要在最后加分號。
14、宏定義和typedef區別?
- 宏主要?于定義常量及書寫復雜的內容; typedef主要?于定義類型別名。
- 宏替換發?在編譯階段之前,屬于?本插?替換; typedef是編譯的?部分。
- 宏不檢查類型; typedef會檢查數據類型。
- 宏不是語句,不在在最后加分號; typedef是語句,要加分號標識結束。
- 注意對指針的操作, typedef char * p_char和#define p_char char *區別巨?。
#define p_char char* // p_char 是宏,替換為 char*int main() {p_char p1, p2; // 替換后:char* p1, p2;char a = 'A';p1 = &a; // p1 是 char*p2 = &a; // 錯誤!p2 是 char(不是指針)return 0;
}
15、變量聲明和定義區別?
- 聲明僅僅是把變量的聲明的位置及類型提供給編譯器,并不分配內存空間;定義要在定義的地?為其分配存儲空間。
- 相同變量可以在多處聲明(外部變量extern),但只能在?處定義。
16、strlen和sizeof區別?
- sizeof是運算符,并不是函數,結果在編譯時得到??運?中獲得; strlen是字符處理的庫函數。
- sizeof參數可以是任何數據的類型或者數據(sizeof參數不退化); strlen的參數只能是字符指針且結尾是’\0’的字符串。
- 因為sizeof值在編譯時確定,所以不能?來得到動態分配(運?時分配)存儲空間的??。
int main(int argc, char const *argv[]){const char* str = "name";sizeof(str); // 取的是指針str的?度,是8strlen(str); // 取的是這個字符串的?度,不包含結尾的 \0。??是4return 0;
}
17、常量指針和指針常量區別?
- 指針常量是?個指針,讀成常量的指針,指向?個只讀變量,也就是后?所指明的int const 和 const int,都是?個常量,可以寫作int const *p或const int *p。
- 常量指針是?個不能給改變指向的指針。指針是個常量,必須初始化,?旦初始化完成,它的值(也就是存放在指針中的地址)就不能在改變了,即不能中途改變指向,如int *const p。
18、a和&a有什么區別?
假設數組int a[10]; int (*p)[10] = &a;其中:
- a是數組名,是數組?元素地址, +1表示地址值加上?個int類型的??,如果a的值是0x00000001,加1操作
后變為0x00000005。 *(a + 1) = a[1]。 - &a是數組的指針,其類型為int (*)[10](就是前?提到的數組指針),其加1時,系統會認為是數組?地址加
上整個數組的偏移(10個int型變量),值為數組a尾元素后?個元素的地址。 - 若(int *)p ,此時輸出 *p時,其值為a[0]的值,因為被轉為int *類型,解引?時按照int類型??來讀取。
19、C++和C語?的區別
- C++中new和delete是對內存分配的運算符,取代了C中的malloc和free。
- 標準C++中的字符串類取代了標準C函數庫頭?件中的字符數組處理函數(C中沒有字符串類型)。
- C++中?來做控制態輸?輸出的iostream類庫替代了標準C中的stdio函數庫。
- C++中的try/catch/throw異常處理機制取代了標準C中的setjmp()和longjmp()函數。C++ 的 try-catch是一種??結構化異常處理(Structured Exception Handling, SEH)??機制,用于在運行時檢測和處理錯誤。它的核心原理是:??當 try塊中的代碼拋出異常時,程序立即跳轉到匹配的 catch塊,并跳過 try塊剩余代碼??。
- 在C++中,允許有相同的函數名,不過它們的參數類型不能完全相同,這樣這些函數就可以相互區別開來。?
這在C語?中是不允許的。也就是C++可以重載, C語?不允許。 - C++語?中,允許變量定義語句在程序中的任何地?,只要在是使?它之前就可以;?C語?中,必須要在函
數開頭部分。?且C++不允許重復定義變量, C語?也是做不到這?點的
C(C89標準):要求變量定義必須集中在塊的開頭(所有定義必須在第一個可執行語句之前)。例如:
void func() {int a; // 必須在開頭a = 10;int b; // 這樣在C89中是錯誤的
}
- 在C++中,除了值和指針之外,新增了引?。引?型變量是其他變量的?個別名,我們可以認為他們只是名字
不相同,其他都是相同的。
20、C++中struct和class的區別
相同點:
- 兩者都擁有成員函數、公有和私有部分
- 任何可以使?class完成的?作,同樣可以使?struct完成
不同點:
- 兩者中如果不對成員不指定公私有, struct默認是公有的, class則默認是私有的
- class默認是private繼承,?struct模式是public繼承
21、define宏定義和const的區別
編譯階段:
- define是在編譯的預處理階段起作?,?const是在編譯、運?的時候起作?
安全性:
- define只做替換,不做類型檢查和計算,也不求解,容易產?錯誤,?般最好加上?個?括號包含住全部的內
容,要不然很容易出錯 - const常量有數據類型,編譯器可以對其進?類型安全檢查
內存占?:
- define只是將宏名稱進?替換,在內存中會產?多分相同的備份。 const在程序運?中只有?份備份,且可以
- 執?常量折疊,能將復雜的的表達式計算出結果放?常量表
- 宏替換發?在編譯階段之前,屬于?本插?替換; const作?發?于編譯過程中。
- 宏不檢查類型; const會檢查數據類型。
- 宏定義的數據沒有分配內存空間,只是插?替換掉; const定義的變量只是值不能改變,但要分配內存空間。
22、C++中const和static的作?
static:
- 不考慮類的情況
- 隱藏。所有不加static的全局變量和函數具有全局可?性,可以在其他?件中使?,加了之后只能
在該?件所在的編譯模塊中使? - 默認初始化為0,包括未初始化的全局靜態變量與局部靜態變量,都存在全局未初始化區
- 靜態變量在函數內定義,始終存在,且只進??次初始化,具有記憶性,其作?范圍與局部變量相同,函數退出后仍然存在,但不能使?
- 隱藏。所有不加static的全局變量和函數具有全局可?性,可以在其他?件中使?,加了之后只能
- 考慮類的情況
- static成員變量:只與類關聯,不與類的對象關聯。定義時要分配空間,不能在類聲明中初始化,必須在類定義體外部初始化,初始化時不需要標示為static;可以被?static成員函數任意訪問。
- static成員函數:不具有this指針,?法訪問類對象的?static成員變量和?static成員函數; 不能被
聲明為const、虛函數和volatile;可以被?static成員函數任意訪問
//static成員變量例子
#include <iostream>class Counter {
public:static int count; // 聲明 static 成員變量Counter() { count++; }static void printCount() { std::cout << "Count: " << count << std::endl; }
};int Counter::count = 0; // 必須在類外初始化(C++17 前)int main() {Counter c1, c2, c3;Counter::printCount(); // 輸出 Count: 3return 0;
}
//static成員函數例子
#include <iostream>class MathUtils {
public:static int add(int a, int b) { return a + b; } // static 成員函數void normalFunc() {std::cout << add(2, 3) << std::endl; // 可以調用 static 函數}
};int main() {std::cout << MathUtils::add(5, 7) << std::endl; // 直接通過類名調用MathUtils obj;obj.normalFunc(); // 輸出 5return 0;
}
const:
- 不考慮類的情況
- const常量在定義時必須初始化,之后?法更改
- const形參可以接收const和非const類型的實參,例如// i 可以是 int 型或者 const int 型void fun(const int& i){ //…}
- 考慮類的情況
- const成員變量:不能在類定義外部初始化,只能通過構造函數初始化列表進?初始化,并且必須有構造函數;不同類對其const數據成員的值可以不同,所以不能在類中聲明時初始化
- const成員函數: const對象不可以調?非const成員函數;非const對象都可以調用;不可以改變非mutable(?該關鍵字聲明的變量可以在const成員函數中被修改)數據的值
//const成員函數
#include <iostream>class Student {
public:void setName(std::string name) { m_name = name; } // 非 const 函數std::string getName() const { return m_name; } // const 函數private:std::string m_name;
};int main() {const Student s1; // const 對象// s1.setName("Alice"); // 錯誤!const 對象不能調用非 const 函數std::cout << s1.getName() << std::endl; // 正確,調用 const 函數Student s2; // 非 const 對象s2.setName("Bob"); // 正確std::cout << s2.getName() << std::endl; // 正確return 0;
}
23、C++的頂層const和底層const
- 概念區分
- 頂層const:指的是const修飾的變量本身是?個常量,?法修改,指的是指針,就是 * 號的右邊
- 底層const:指的是const修飾的變量所指向的對象是?個常量,指的是所指變量,就是 * 號的左邊
- 舉個例?
int a = 10;int* const b1 = &a; //頂層const, b1本身是?個常量
const int* b2 = &a; //底層const, b2本身可變,所指的對象是常量
const int b3 = 20; //頂層const, b3是常量不可變
const int* const b4 = &a; //前?個const為底層,后?個為頂層, b4不可變
const int& b5 = a; //?于聲明引?變量,都是底層const
24、數組名和指針(這?為指向數組?元素的指針)區別?
- ?者均可通過增減偏移量來訪問數組中的元素。
- 數組名不是真正意義上的指針,可以理解為常指針,所以數組名沒有?增、?減等操作。
- 當數組名當做形參傳遞給調?函數后,就失去了原有特性,退化成?般指針,多了?增、?減操作,但sizeof運算符不能再得到原數組的??了。
數組名會退化為指向首元素的指針??:
void func(int arr[]) { // 等價于 int *arr// 此時 arr 是一個指針,不是數組
}
??sizeof(arr)返回的是指針的大小??(通常是 4或 8字節),而不是數組的大小。
25、拷?初始化和直接初始化
- 當?于類類型對象時,初始化的拷?形式和直接形式有所不同:直接初始化直接調?與實參匹配的構造函數,
拷?初始化總是調?拷?構造函數。拷?初始化?先使?指定構造函數創建?個臨時對象,然后?拷?構造函
數將那個臨時對象拷?到正在創建的對象。舉例如下:
string str1("I am a string");//語句1 直接初始化
string str2(str1);//語句2 直接初始化, str1是已經存在的對象,直接調?拷?構造函數對str2進?初始化
string str3 = "I am a string";//語句3 拷?初始化,先為字符串”I am a string“創建臨時對象,再把臨
時對象作為參數,使?拷?構造函數構造str3
string str4 = str1;//語句4 拷?初始化,這?相當于隱式調?拷?構造函數,?不是調?賦值運算符函數
- 為了提?效率,允許編譯器跳過創建臨時對象這?步, 直接調?構造函數構造要創建的對象,這樣就完全等價
于直接初始化了(語句1和語句3等價)
26、初始化和賦值的區別
- 對于簡單類型來說,初始化和賦值沒什么區別
- 對于類和復雜數據類型來說,這兩者的區別就?了,舉例如下: