1 變數的宣告和定義有什么區別
變數的定義為變數分配地址和存盤空間, 變數的宣告不分配地址,一個變數可以在多個地方宣告, 但是只在一個地方定義,加入extern 修飾的是變數的宣告,說明此變數將在檔案以外或在檔案后面部分定義,
說明:很多時候一個變數,只是宣告不分配記憶體空間,直到具體使用時才初始化,分配記憶體空間, 如外部變數,
int main()
{
extern int A;
//這是個宣告而不是定義,宣告A是一個已經定義了的外部變數
//注意:宣告外部變數時可以把變數型別去掉如:extern A;
dosth(); //執行函式
}
int A; //是定義,定義了A為整型的外部變數
2 簡述#ifdef、#else、#endif和#ifndef的作用
利用#ifdef、#endif將某程式功能模塊包括進去,以向特定用戶提供該功能,在不需要時用戶可輕易將其屏蔽,
#ifdef MATH
#include "math.c"
#endif
在子程式前加上標記,以便于追蹤和除錯,
#ifdef DEBUG
printf ("Indebugging......!");
#endif
應對硬體的限制,由于一些具體應用環境的硬體不一樣,限于條件,本地缺乏這種設備,只能繞過硬體,直接寫出預期結果,
「注意」:雖然不用條件編譯命令而直接用if陳述句也能達到要求,但那樣做目標程式長(因為所有陳述句都編譯),運行時間長(因為在程式運行時間對if陳述句進行測驗),而采用條件編譯,可以減少被編譯的陳述句,從而減少目標程式的長度,減少運行時間,
3 寫出int 、bool、 float 、指標變數與 “零值”比較的if 陳述句
//int與零值比較
if ( n == 0 )
if ( n != 0 )
//bool與零值比較
if (flag) // 表示flag為真
if (!flag) // 表示flag為假
//float與零值比較
const float EPSINON = 0.00001;
if ((x >= - EPSINON) && (x <= EPSINON) //其中EPSINON是允許的誤差(即精度),
//指標變數與零值比較
if (p == NULL)
if (p != NULL)
4 結構體可以直接賦值嗎
宣告時可以直接初始化,同一結構體的不同物件之間也可以直接賦值,但是當結構體中含有指標“成員”時一定要小心,
「注意」:當有多個指標指向同一段記憶體時,某個指標釋放這段記憶體可能會導致其他指標的非法操作,因此在釋放前一定要確保其他指標不再使用這段記憶體空間,
5 sizeof 和strlen 的區別
-
sizeof是一個運算子,strlen是庫函式,
-
sizeof的引數可以是資料的型別,也可以是變數,而strlen只能以結尾為‘\0’的字串作引數,
-
編譯器在編譯時就計算出了sizeof的結果,而strlen函式必須在運行時才能計算出來,并且sizeof計算的是資料型別占記憶體的大小,而strlen計算的是字串實際的長度,
-
陣列做sizeof的引數不退化,傳遞給strlen就退化為指標了
6 C 語言的關鍵字 static 和 C++ 的關鍵字 static 有什么區別
在 C 中 static 用來修飾區域靜態變數和外部靜態變數、函式,而 C++中除了上述功能外,還用來定義類的成員變數和函式,即靜態成員和靜態成員函式,
「注意」:編程時 static 的記憶性,和全域性的特點可以讓在不同時期呼叫的函式進行通信,傳遞資訊,而 C++的靜態成員則可以在多個物件實體間進行通信,傳遞資訊,
7 C 語言的 malloc 和 C++ 中的 new 有什么區別
-
new 、delete 是運算子,可以多載,只能在C++ 中使用,
-
malloc、free 是函式,可以覆寫,C、C++ 中都可以使用,
-
new 可以呼叫物件的建構式,對應的delete 呼叫相應的解構式,
-
malloc 僅僅分配記憶體,free 僅僅回收記憶體,并不執行構造和解構式
-
new 、delete 回傳的是某種資料型別指標,malloc、free 回傳的是void 指標,
「注意」:malloc 申請的記憶體空間要用free 釋放,而new 申請的記憶體空間要用delete 釋放,不要混用,
8 寫一個 “標準”宏MIN
#define min(a,b)((a)<=(b)?(a):(b))
9 ++i和i++的區別
++i先自增1,再回傳,i++先回傳i,再自增1
10 volatile有什么作用
-
狀態暫存器一類的并行設備硬體暫存器,
-
一個中斷服務子程式會訪問到的非自動變數,
-
多執行緒間被幾個任務共享的變數,
「注意」:雖然volatile在嵌入式方面應用比較多,但是在PC軟體的多執行緒中,volatile修飾的臨界變數也是非常實用的,
11 一個引數可以既是const又是volatile嗎
可以,用const和volatile同時修飾變數,表示這個變數在程式內部是只讀的,不能改變的,只在程式外部條件變化下改變,并且編譯器不會優化這個變數,每次使用這個變數時,都要小心地去記憶體讀取這個變數的值,而不是去暫存器讀取它的備份,
注意:在此一定要注意const的意思,const只是不允許程式中的代碼改變某一變數,其在編譯期發揮作用,它并沒有實際地禁止某段記憶體的讀寫特性,
12 a 和&a 有什么區別
&a:其含義就是“變數a的地址”,
*a:用在不同的地方,含義也不一樣,
-
在宣告陳述句中,*a只說明a是一個指標變數,如int *a;
-
在其他陳述句中,*a前面沒有運算元且a是一個指標時,*a代表指標a指向的地址記憶體放的資料,如b=*a;
-
*a前面有運算元且a是一個普通變數時,a代表乘以a,如c=ba,
13 用C 撰寫一個死回圈程式
while(1)
{ }
「注意」:很多種途徑都可實作同一種功能,但是不同的方法時間和空間占用度不同,特別是對于嵌入 式軟體,處理器速度比較慢,存盤空間較小,所以時間和空間優勢是選擇各種方法的首要考慮條件,
14 結構體記憶體對齊問題
請寫出以下代碼的輸出結果:
#include<stdio.h>
struct S1
{
int i:8;
char j:4;
int a:4;
double b;
};
struct S2
{
int i:8;
char j:4;
double b;
int a:4;
};
struct S3
{
int i;
char j;
double b;
int a;
};
int main()
{
printf("%d\n",sizeof(S1)); // 輸出8
printf("%d\n",sizeof(S1); // 輸出12
printf("%d\n",sizeof(Test3)); // 輸出8
return 0;
}
sizeof(S1)=16
sizeof(S2)=24
sizeof(S3)=32
「說明」:結構體作為一種復合資料型別,其構成元素既可以是基本資料型別的變數,也可以是一些復合型型別資料,對此,編譯器會自動進行成員變數的對齊以提高運算效率,默認情況下,按自然對齊條件分配空間,各個成員按照它們被宣告的順序在記憶體中順序存盤,第一個成員的地址和整個結構的地址相同,向結構體成員中size最大的成員對齊,
許多實際的計算機系統對基本型別資料在記憶體中存放的位置有限制,它們會要求這些資料的首地址的值是某個數k(通常它為4或8)的倍數,而這個k則被稱為該資料型別的對齊模數,
15 全域變數和區域變數有什么區別?實怎么實作的?作業系統和編譯器是怎么知道的?
-
全域變數是整個程式都可訪問的變數,誰都可以訪問,生存期在整個程式從運行到結束(在程式結束時所占記憶體釋放);
-
而區域變數存在于模塊(子程式,函式)中,只有所在模塊可以訪問,其他模塊不可直接訪問,模塊結束(函式呼叫完畢),區域變數消失,所占據的記憶體釋放,
-
作業系統和編譯器,可能是通過記憶體分配的位置來知道的,全域變數分配在全域資料段并且在程式開始運行的時候被加載.區域變數則分配在堆疊里面,
16 簡述C、C++程式編譯的記憶體分配情況
-
從靜態存盤區域分配:
記憶體在程式編譯時就已經分配好,這塊記憶體在程式的整個運行期間都存在,速度快、不容易出錯, 因為有系統會善后,例如全域變數,static 變數,常量字串等,
-
在堆疊上分配:
在執行函式時,函式內區域變數的存盤單元都在堆疊上創建,函式執行結束時這些存盤單元自動被釋 放,堆疊記憶體分配運算內置于處理器的指令集中,效率很高,但是分配的記憶體容量有限,大小為2M,
-
從堆上分配:
即動態記憶體分配,程式在運行的時候用 malloc 或new 申請任意大小的記憶體,程式員自己負責在何 時用free 或delete 釋放記憶體,動態記憶體的生存期由程式員決定,使用非常靈活,如果在堆上分配了空間,就有責任回收它,否則運行的程式會出現記憶體泄漏,另外頻繁地分配和釋放不同大小的堆空間將會產生 堆內碎塊,
一個C、C++程式編譯時記憶體分為5 大存盤區:堆區、堆疊區、全域區、文字常量區、程式代碼區,
17 簡述strcpy、sprintf 與memcpy 的區別
-
操作物件不同,strcpy 的兩個操作物件均為字串,sprintf 的操作源物件可以是多種資料型別, 目的操作物件是字串,memcpy 的兩個物件就是兩個任意可操作的記憶體地址,并不限于何種資料型別,
-
執行效率不同,memcpy 最高,strcpy 次之,sprintf 的效率最低,
-
實作功能不同,strcpy 主要實作字串變數間的拷貝,sprintf 主要實作其他資料型別格式到字 符串的轉化,memcpy 主要是記憶體塊間的拷貝,
「注意」:strcpy、sprintf 與memcpy 都可以實作拷貝的功能,但是針對的物件不同,根據實際需求,來 選擇合適的函式實作拷貝功能,
18 請決議((void ()( ) )0)( )的含義
-
void (*0)( ) :是一個回傳值為void,引數為空的函式指標0,
-
(void (*)( ))0:把0轉變成一個回傳值為void,引數為空的函式指標,
-
(void ()( ))0:在上句的基礎上加*表示整個是一個回傳值為void,無引數,并且起始地址為0的函式的名字,
-
((void ()( ))0)( ):這就是上句的函式名所對應的函式的呼叫,
19 C語言的指標和參考和c++的有什么區別?
-
指標有自己的一塊空間,而參考只是一個別名;
-
使用sizeof看一個指標的大小是4,而參考則是被參考物件的大小;
-
作為引數傳遞時,指標需要被解參考才可以對物件進行操作,而直接對引 用的修改都會改變參考所指向的物件;
-
可以有const指標,但是沒有const參考;
-
指標在使用中可以指向其它物件,但是參考只能是一個物件的參考,不能 被改變;
-
指標可以有多級指標(**p),而參考止于一級;
-
指標和參考使用++運算子的意義不一樣;
-
如果回傳動態記憶體分配的物件或者記憶體,必須使用指標,參考可能引起記憶體泄露,
20 typedef 和define 有什么區別
-
用法不同:typedef 用來定義一種資料型別的別名,增強程式的可讀性,define 主要用來定義 常量,以及書寫復雜使用頻繁的宏,
-
執行時間不同:typedef 是編譯程序的一部分,有型別檢查的功能,define 是宏定義,是預編譯的部分,其發生在編譯之前,只是簡單的進行字串的替換,不進行型別的檢查,
-
作用域不同:typedef 有作用域限定,define 不受作用域約束,只要是在define 宣告后的參考 都是正確的,
-
對指標的操作不同:typedef 和define 定義的指標時有很大的區別,
「注意」:typedef 定義是陳述句,因為句尾要加上分號,而define 不是陳述句,千萬不能在句尾加分號,
21 指標常量與常量指標區別
指標常量是指定義了一個指標,這個指標的值只能在定義時初始化,其他地方不能改變,常量指標 是指定義了一個指標,這個指標指向一個只讀的物件,不能通過常量指標來改變這個物件的值,指標常量強調的是指標的不可改變性,而常量指標強調的是指標對其所指物件的不可改變性,
「注意」:無論是指標常量還是常量指標,其最大的用途就是作為函式的形式引數,保證實參在被呼叫 函式中的不可改變特性,
22 簡述佇列和堆疊的異同
佇列和堆疊都是線性存盤結構,但是兩者的插入和洗掉資料的操作不同,佇列是“先進先出”,堆疊是 “后進先出”,
「注意」:區別堆疊區和堆區,堆區的存取是“順序隨意”,而堆疊區是“后進先出”,堆疊由編譯器自動分 配釋放 ,存放函式的引數值,區域變數的值等,其操作方式類似于資料結構中的堆疊,堆一般由程式員 分配釋放, 若程式員不釋放,程式結束時可能由OS 回收,分配方式類似于鏈表,它與本題中的堆和堆疊是兩回事,堆疊只是一種資料結構,而堆區和堆疊區是程式的不同記憶體存盤區域,
23 設定地址為0x67a9 的整型變數的值為0xaa66
int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa66;
「注意」:這道題就是強制型別轉換的典型例子,無論在什么平臺地址長度和整型資料的長度是一樣的, 即一個整型資料可以強制轉換成地址指標型別,只要有意義即可,
24 編碼實作字串轉化為數字
編碼實作函式atoi(),設計一個程式,把一個字串轉化為一個整型數值,例如數字:“5486321 ”, 轉化成字符:5486321,
int myAtoi(const char * str)
{
int num = 0; //保存轉換后的數值
int isNegative = 0; //記錄字串中是否有負號
int n =0;
char *p = str;
if(p == NULL) //判斷指標的合法性
{
return -1;
}
while(*p++ != '\0') //計算數字串度
{
n++;
}
p = str;
if(p[0] == '-') //判斷陣列是否有負號
{
isNegative = 1;
}
char temp = '0';
for(int i = 0 ; i < n; i++)
{
char temp = *p++;
if(temp > '9' ||temp < '0') //濾除非數字字符
{
continue;
}
if(num !=0 || temp != '0') //濾除字串開始的0 字符
{
temp -= 0x30; //將數字字符轉換為數值
num += temp *int( pow(10 , n - 1 -i) );
}
}
if(isNegative) //如果字串中有負號,將數值取反
{
return (0 - num);
}
else
{
return num; //回傳轉換后的數值
}
}
25 C語言的結構體和C++的有什么區別
-
C語言的結構體是不能有函式成員的,而C++的類可以有,
-
C語言的結構體中資料成員是沒有private、public和protected訪問限定的,而C++的類的成員有這些訪問限定,
-
C語言的結構體是沒有繼承關系的,而C++的類卻有豐富的繼承關系,
「注意」:雖然C的結構體和C++的類有很大的相似度,但是類是實作面向物件的基礎,而結構體只可以簡單地理解為類的前身,
26 簡述指標常量與常量指標的區別
-
指標常量是指定義了一個指標,這個指標的值只能在定義時初始化,其他地方不能改變,常量指標是指定義了一個指標,這個指標指向一個只讀的物件,不能通過常量指標來改變這個物件的值,
-
指標常量強調的是指標的不可改變性,而常量指標強調的是指標對其所指物件的不可改變性,
「注意」:無論是指標常量還是常量指標,其最大的用途就是作為函式的形式引數,保證實參在被呼叫函式中的不可改變特性,
27 如何避免“野指標”
-
指標變數宣告時沒有被初始化,解決辦法:指標宣告時初始化,可以是具體的地址值,也可讓它指向NULL,
-
指標p被free或者delete之后,沒有置為NULL,解決辦法:指標指向的記憶體空間被釋放后指標應該指向NULL,
-
指標操作超越了變數的作用范圍,解決辦法:在變數的作用域結束前釋放掉變數的地址空間并且讓指標指向NULL,
28 句柄和指標的區別和聯系是什么?
句柄和指標其實是兩個截然不同的概念,Windows系統用句柄標記系統資源,隱藏系統的資訊,你只要知道有這個東西,然后去呼叫就行了,它是個32it的uint,指標則標記某個物理記憶體地址,兩者是不同的概念,
29 new/delete與malloc/free的區別是什么
-
new能自動計算需要分配的記憶體空間,而malloc需要手工計算位元組數,
int *p = new int[2];
int *q = (int *)malloc(2*sizeof(int));
-
new與delete直接帶具體型別的指標,malloc和free回傳void型別的指標,
-
new型別是安全的,而malloc不是,例如int *p = new float[2];就會報錯;而int p = malloc(2sizeof(int))編譯時編譯器就無法指出錯誤來,
-
new一般分為兩步:new操作和構造,new操作對應與malloc,但new操作可以多載,可以自定義記憶體分配策略,不做記憶體分配,甚至分配到非記憶體設備上,而malloc不行,
-
new呼叫建構式,malloc不能;delete呼叫解構式,而free不能,
-
malloc/free需要庫檔案stdlib.h的支持,new/delete則不需要!
「注意」:delete和free被呼叫后,記憶體不會立即回收,指標也不會指向空,delete或free僅僅是告訴作業系統,這一塊記憶體被釋放了,可以用作其他用途,但是由于沒有重新對這塊記憶體進行寫操作,所以記憶體中的變數數值并沒有發生變化,出現野指標的情況,因此,釋放完記憶體后,應該講該指標指向NULL,
30 說一說extern“C”
extern "C"的主要作用就是為了能夠正確實作C++代碼呼叫其他C語言代碼,加上extern "C"后,會指示編譯器這部分代碼按C語言(而不是C++)的方式進行編譯,由于C++支持函式多載,因此編譯器編譯函式的程序中會將函式的引數型別也加到編譯后的代碼中,而不僅僅是函式名;而C語言并不支持函式多載,因此編譯C語言代碼的函式時不會帶上函式的引數型別,一般只包括函式名,
這個功能十分有用處,因為在C++出現以前,很多代碼都是C語言寫的,而且很底層的庫也是C語言寫的,為了更好的支持原來的C代碼和已經寫好的C語言庫,需要在C++中盡可能的支持C,而extern "C"就是其中的一個策略,
-
C++代碼呼叫C語言代碼
-
在C++的頭檔案中使用
-
在多個人協同開發時,可能有的人比較擅長C語言,而有的人擅長C++,這樣的情況下也會有用到
31 請你來說一下C++中struct和class的區別
在C++中,class和struct做型別定義是只有兩點區別:
-
默認繼承權限不同,class繼承默認是private繼承,而struct默認是public繼承
-
class還可用于定義模板引數,像typename,但是關鍵字struct不能同于定義模板引數 C++保留struct關鍵字,原因
-
保證與C語言的向下兼容性,C++必須提供一個struct
-
C++中的struct定義必須百分百地保證與C語言中的struct的向下兼容性,把C++中的最基本的物件單元規定為class而不是struct,就是為了避免各種兼容性要求的限制
-
對struct定義的擴展使C語言的代碼能夠更容易的被移植到C++中
32 C++類內可以定義參考資料成員嗎?
可以,必須通過成員函式初始化串列初始化,
33 C++中類成員的訪問權限
C++通過 public、protected、private 三個關鍵字來控制成員變數和成員函式的訪問權限,它們分別表示公有的、受保護的、私有的,被稱為成員訪問限定符,在類的內部(定義類的代碼內部),無論成員被宣告為 public、protected 還是 private,都是可以互相訪問的,沒有訪問權限的限制,在類的外部(定義類的代碼之外),只能通過物件訪問成員,并且通過物件只能訪問 public 屬性的成員,不能訪問 private、protected 屬性的成員
34 什么是右值參考,跟左值又有什么區別?
左值和右值的概念:
-
左值:能取地址,或者具名物件,運算式結束后依然存在的持久物件;
-
右值:不能取地址,匿名物件,運算式結束后就不再存在的臨時物件;區別:
-
左值能賦值,右值不能;
-
左值可變,右值不能(僅對基礎型別適用,用戶自定義型別右值參考可以通過成員函式改變);
35 面向物件的三大特征
-
封裝性:將客觀事物抽象成類,每個類對自身的資料和方法實行 protection (private , protected , public ),
-
繼承性:廣義的繼承有三種實作形式:實作繼承(使用基類的屬性和方法而無需額外編碼的能力)、可 視繼承(子表單使用父表單的外觀和實作代碼)、介面繼承(僅使用屬性和方法,實作滯后到子類實作),
-
多型性:是將父類物件設定成為和一個或更多它的子物件相等的技術,用子類物件給父類物件賦值 之后,父類物件就可以根據當前賦值給它的子物件的特性以不同的方式運作,
36 說一說c++中四種cast轉換
C++中四種型別轉換是:static_cast, dynamic_cast, const_cast, reinterpret_cast
1、const_cast
-
用于將const變數轉為非const
2、static_cast
-
用于各種隱式轉換,比如非const轉const,void*轉指標等, static_cast能用于多型向上轉化,如果向下轉能成功但是不安全,結果未知;
3、dynamic_cast
用于動態型別轉換,只能用于含有虛函式的類,用于類層次間的向上和向下轉化,只能轉指標或參考,向下轉化時,如果是非法的***對于指標回傳NULL,對于參考拋例外***,要深入了解內部轉換的原理,
-
向上轉換:指的是子類向基類的轉換
-
向下轉換:指的是基類向子類的轉換
它通過判斷在執行到該陳述句的時候變數的運行時型別和要轉換的型別是否相同來判斷是否能夠進行向下轉換,
4、reinterpret_cast
-
幾乎什么都可以轉,比如將int轉指標,可能會出問題,盡量少用;
5、為什么不使用C的強制轉換?
-
C的強制轉換表面上看起來功能強大什么都能轉,但是轉化不夠明確,不能進行錯誤檢查,容易出錯,
37 C++的空類有哪些成員函式
-
預設建構式,
-
預設拷貝建構式,
-
省解構式,
-
賦值運算子,
-
取址運算子,
-
取址運算子 const ,
「注意」:有些書上只是簡單的介紹了前四個函式,沒有提及后面這兩個函式,但后面這兩個函式也是 空類的默認函式,另外需要注意的是,只有當實際使用這些函式的時候,編譯器才會去定義它們,
38 對c++中的smart pointer四個智能指標:shared_ptr,unique_ptr,weak_ptr,auto_ptr的理解
C++里面的四個智能指標: auto_ptr, shared_ptr, weak_ptr, unique_ptr 其中后三個是c++11支持,并且第一個已經被11棄用,
智能指標的作用是管理一個指標,因為存在以下這種情況:申請的空間在函式結束時忘記釋放,造成記憶體泄漏,使用智能指標可以很大程度上的避免這個問題,因為智能指標就是一個類,當超出了類的作用域是,類會自動呼叫解構式,解構式會自動釋放資源,所以智能指標的作用原理就是在函式結束時自動釋放記憶體空間,不需要手動釋放記憶體空間,
-
auto_ptr(c++98的方案,cpp11已經拋棄)
采用所有權模式,
auto_ptr< string> p1 (new string ("I reigned lonely as a cloud.”));
auto_ptr<string> p2;
p2 = p1; //auto_ptr不會報錯.
此時不會報錯,p2剝奪了p1的所有權,但是當程式運行時訪問p1將會報錯,所以auto_ptr的缺點是:存在潛在的記憶體崩潰問題!
-
unique_ptr(替換auto_ptr)
unique_ptr實作獨占式擁有或嚴格擁有概念,保證同一時間內只有一個智能指標可以指向該物件,它對于避免資源泄露(例如“以new創建物件后因為發生例外而忘記呼叫delete”)特別有用,
采用所有權模式,
unique_ptr<string> p3 (new string ("auto")); //#4
unique_ptr<string> p4; //#5
p4 = p3;//此時會報錯!!
編譯器認為p4=p3非法,避免了p3不再指向有效資料的問題,因此,unique_ptr比auto_ptr更安全,
另外unique_ptr還有更聰明的地方:當程式試圖將一個 unique_ptr 賦值給另一個時,如果源 unique_ptr 是個臨時右值,編譯器允許這么做;如果源 unique_ptr 將存在一段時間,編譯器將禁止這么做,比如:
unique_ptr<string> pu1(new string ("hello world"));
unique_ptr<string> pu2;
pu2 = pu1; // #1 not allowed
unique_ptr<string> pu3;
pu3 = unique_ptr<string>(new string ("You")); // #2 allowed
其中#1留下懸掛的unique_ptr(pu1),這可能導致危害,而#2不會留下懸掛的unique_ptr,因為它呼叫 unique_ptr 的建構式,該建構式創建的臨時物件在其所有權讓給 pu3 后就會被銷毀,這種隨情況而已的行為表明,unique_ptr 優于允許兩種賦值的auto_ptr ,
「注意」:如果確實想執行類似與#1的操作,要安全的重用這種指標,可給它賦新值,C++有一個標準庫函式std::move(),讓你能夠將一個unique_ptr賦給另一個,例如:
unique_ptr<string> ps1, ps2;
ps1 = demo("hello");
ps2 = move(ps1);
ps1 = demo("alexia");
cout << *ps2 << *ps1 << endl;
-
shared_ptr
shared_ptr實作共享式擁有概念,多個智能指標可以指向相同物件,該物件和其相關資源會在“最后一個參考被銷毀”時候釋放,從名字share就可以看出了資源可以被多個指標共享,它使用計數機制來表明資源被幾個指標共享,可以通過成員函式use_count()來查看資源的所有者個數,除了可以通過new來構造,還可以通過傳入auto_ptr, unique_ptr,weak_ptr來構造,當我們呼叫release()時,當前指標會釋放資源所有權,計數減一,當計數等于0時,資源會被釋放,
shared_ptr 是為了解決 auto_ptr 在物件所有權上的局限性(auto_ptr 是獨占的), 在使用參考計數的機制上提供了可以共享所有權的智能指標,
成員函式:
use_count 回傳參考計數的個數
unique 回傳是否是獨占所有權( use_count 為 1)
swap 交換兩個 shared_ptr 物件(即交換所擁有的物件)
reset 放棄內部物件的所有權或擁有物件的變更, 會引起原有物件的參考計數的減少
get 回傳內部物件(指標), 由于已經多載了()方法, 因此和直接使用物件是一樣的.如 shared_ptrsp(new int(1)); sp 與 sp.get()是等價的
-
weak_ptr
weak_ptr 是一種不控制物件生命周期的智能指標, 它指向一個 shared_ptr 管理的物件. 進行該物件的記憶體管理的是那個強參考的 shared_ptr. weak_ptr只是提供了對管理物件的一個訪問手段,weak_ptr 設計的目的是為配合 shared_ptr 而引入的一種智能指標來協助 shared_ptr 作業, 它只可以從一個 shared_ptr 或另一個 weak_ptr 物件構造, 它的構造和析構不會引起參考記數的增加或減少,weak_ptr是用來解決shared_ptr相互參考時的死鎖問題,如果說兩個shared_ptr相互參考,那么這兩個指標的參考計數永遠不可能下降為0,資源永遠不會釋放,它是對物件的一種弱參考,不會增加物件的參考計數,和shared_ptr之間可以相互轉化,shared_ptr可以直接賦值給它,它可以通過呼叫lock函式來獲得shared_ptr,
class B;
class A
{
public:
shared_ptr<B> pb_;
~A()
{
cout<<"A delete
";
}
};
class B
{
public:
shared_ptr<A> pa_;
~B()
{
cout<<"B delete
";
}
};
void fun()
{
shared_ptr<B> pb(new B());
shared_ptr<A> pa(new A());
pb->pa_ = pa;
pa->pb_ = pb;
cout<<pb.use_count()<<endl;
cout<<pa.use_count()<<endl;
}
int main()
{
fun();
return 0;
}
可以看到fun函式中pa ,pb之間互相參考,兩個資源的參考計數為2,當要跳出函式時,智能指標pa,pb析構時兩個資源參考計數會減一,但是兩者參考計數還是為1,導致跳出函式時資源沒有被釋放(A B的解構式沒有被呼叫),如果把其中一個改為weak_ptr就可以了,我們把類A里面的shared_ptr pb_; 改為weak_ptr pb_; 運行結果如下,這樣的話,資源B的參考開始就只有1,當pb析構時,B的計數變為0,B得到釋放,B釋放的同時也會使A的計數減一,同時pa析構時使A的計數減一,那么A的計數為0,A得到釋放,
「注意」:不能通過weak_ptr直接訪問物件的方法,比如B物件中有一個方法print(),我們不能這樣訪問,pa->pb_->print(); 英文pb_是一個weak_ptr,應該先把它轉化為shared_ptr,如:shared_ptr p = pa->pb_.lock(); p->print();
39 說說強制型別轉換運算子
「static_cast」
-
用于非多型型別的轉換
-
不執行運行時型別檢查(轉換安全性不如 dynamic_cast)
-
通常用于轉換數值資料型別(如 float -> int)
-
可以在整個類層次結構中移動指標,子類轉化為父類安全(向上轉換),父類轉化為子類不安全(因為子類可能有不在父類的欄位或方法)
「dynamic_cast」
-
用于多型型別的轉換
-
執行行運行時型別檢查
-
只適用于指標或參考
-
對不明確的指標的轉換將失敗(回傳 nullptr),但不引發例外
-
可以在整個類層次結構中移動指標,包括向上轉換、向下轉換
「const_cast」
-
用于洗掉 const、volatile 和 __unaligned 特性(如將 const int 型別轉換為 int 型別 ) reinterpret_cast
-
用于位的簡單重新解釋
-
濫用 reinterpret_cast 運算子可能很容易帶來風險,除非所需轉換本身是低級別的,否則應- 使用其他強制轉換運算子之一,
-
允許將任何指標轉換為任何其他指標型別(如 char* 到 int* 或 One_class* 到 Unrelated_class* 之類的轉換,但其本身并不安全)
-
也允許將任何整數型別轉換為任何指標型別以及反向轉換,
-
reinterpret_cast 運算子不能丟掉 const、volatile 或 __unaligned 特性,
-
reinterpret_cast 的一個實際用途是在哈希函式中,即,通過讓兩個不同的值幾乎不以相同的索引結尾的方式將值映射到索引,
「bad_cast」
-
由于強制轉換為參考型別失敗,dynamic_cast 運算子引發 bad_cast 例外,
bad_cast 使用
try {
Circle& ref_circle = dynamic_cast<Circle&>(ref_shape);
}
catch (bad_cast b) {
cout << "Caught: " << b.what();
}
40 談談你對拷貝建構式和賦值運算子的認識
拷貝建構式和賦值運算子多載有以下兩個不同之處:
-
拷貝建構式生成新的類物件,而賦值運算子不能,
-
由于拷貝建構式是直接構造一個新的類物件,所以在初始化這個物件之前不用檢驗源物件 是否和新建物件相同,而賦值運算子則需要這個操作,另外賦值運算中如果原來的物件中有記憶體分配要先把記憶體釋放掉,
「注意」:當有類中有指標型別的成員變數時,一定要重寫拷貝建構式和賦值運算子,不要使用默認 的,
41 在C++中,使用malloc申請的記憶體能否通過delete釋放?使用new申請的記憶體能否用free?
不能,malloc /free主要為了兼容C,new和delete 完全可以取代malloc /free的,malloc /free的操作物件都是必須明確大小的,而且不能用在動態類上,new 和delete會自動進行型別檢查和大小,malloc/free不能執行建構式與解構式,所以動態物件它是不行的,當然從理論上說使用malloc申請的記憶體是可以通過delete釋放的,不過一般不這樣寫的,而且也不能保證每個C++的運行時都能正常,
42 用C++設計一個不能被繼承的類
template <typename T> class A
{
friend T;
private:
A() {}
~A() {}
};
class B : virtual public A<B>
{
public:
B() {}
~B() {}
};
class C : virtual public B
{
public:
C() {}
~C() {}
};
void main( void )
{
B b;
//C c;
return;
}
「注意」:建構式是繼承實作的關鍵,每次子類物件構造時,首先呼叫的是父類的建構式,然后才 是自己的,
43 C++自己實作一個String類
#include <iostream>
#include <cstring>
using namespace std;
class String{
public:
// 默認建構式
String(const char *str = nullptr);
// 拷貝建構式
String(const String &str);
// 解構式
~String();
// 字串賦值函式
String& operator=(const String &str);
private:
char *m_data;
int m_size;
};
// 建構式
String::String(const char *str)
{
if(str == nullptr) // 加分點:對m_data加NULL 判斷
{
m_data = new char[1]; // 得分點:對空字串自動申請存放結束標志'\0'的
m_data[0] = '\0';
m_size = 0;
}
else
{
m_size = strlen(str);
m_data = new char[m_size + 1];
strcpy(m_data, str);
}
}
// 拷貝建構式
String::String(const String &str) // 得分點:輸入引數為const型
{
m_size = str.m_size;
m_data = new char[m_size + 1]; //加分點:對m_data加NULL 判斷
strcpy(m_data, str.m_data);
}
// 解構式
String::~String()
{
delete[] m_data;
}
// 字串賦值函式
String& String::operator=(const String &str) // 得分點:輸入引數為const
{
if(this == &str) //得分點:檢查自賦值
return *this;
delete[] m_data; //得分點:釋放原有的記憶體資源
m_size = strlen(str.m_data);
m_data = new char[m_size + 1]; //加分點:對m_data加NULL 判斷
strcpy(m_data, str.m_data);
return *this; //得分點:回傳本物件的參考
}
44 訪問基類的私有虛函式
寫出以下程式的輸出結果:
#include <iostream.h>
class A
{
virtual void g()
{
cout << "A::g" << endl;
}
private:
virtual void f()
{
cout << "A::f" << endl;
}
};
class B : public A
{
void g()
{
cout << "B::g" << endl;
}
virtual void h()
{
cout << "B::h" << endl;
}
};
typedef void( *Fun )( void );
void main()
{
B b;
Fun pFun;
for(int i = 0 ; i < 3; i++)
{
pFun = ( Fun )*( ( int* ) * ( int* )( &b ) + i );
pFun();
}
}
輸出結果:
B::g
A::f
B::h
「注意」:考察了面試者對虛函式的理解程度,一個對虛函式不了解的人很難正確的做出本題,在學習面向物件的多型性時一定要深刻理解虛函式表的作業原理,
45 對虛函式和多型的理解
多型的實作主要分為靜態多型和動態多型,靜態多型主要是多載,在編譯的時候就已經確定;動態多型是用虛函式機制實作的,在運行期間動態系結,舉個例子:一個父型別別的指標指向一個子類物件時候,使用父類的指標去呼叫子類中重寫了的父類中的虛函式的時候,會呼叫子類重寫過后的函式,在父類中宣告為加了virtual關鍵字的函式,在子類中重寫時候不需要加virtual也是虛函式,
虛函式的實作:在有虛函式的類中,類的最開始部分是一個虛函式表的指標,這個指標指向一個虛函式表,表中放了虛函式的地址,實際的虛函式在代碼段(.text)中,當子類繼承了父類的時候也會繼承其虛函式表,當子類重寫父類中虛函式時候,會將其繼承到的虛函式表中的地址替換為重新寫的函式地址,使用了虛函式,會增加訪問記憶體開銷,降低效率,
46 簡述類成員函式的重寫、多載和隱藏的區別
(1)重寫和多載主要有以下幾點不同,
-
范圍的區別:被重寫的和重寫的函式在兩個類中,而多載和被多載的函式在同一個類中,
-
引數的區別:被重寫函式和重寫函式的引數串列一定相同,而被多載函式和多載函式的引數串列一 定不同,
-
virtual 的區別:重寫的基類中被重寫的函式必須要有virtual 修飾,而多載函式和被多載函式可以被 virtual 修飾,也可以沒有,
(2)隱藏和重寫、多載有以下幾點不同,
-
與多載的范圍不同:和重寫一樣,隱藏函式和被隱藏函式不在同一個類中,
-
引數的區別:隱藏函式和被隱藏的函式的引數串列可以相同,也可不同,但是函式名肯定要相同,當引數不相同時,無論基類中的引數是否被virtual 修飾,基類的函式都是被隱藏,而不是被重寫,
「注意」:雖然多載和覆寫都是實作多型的基礎,但是兩者實作的技術完全不相同,達到的目的也是完 全不同的,覆寫是動態態系結的多型,而多載是靜態系結的多型,
47 鏈表和陣列有什么區別
-
存盤形式:陣列是一塊連續的空間,宣告時就要確定長度,鏈表是一塊可不連續的動態空間, 長度可變,每個結點要保存相鄰結點指標,
-
資料查找:陣列的線性查找速度快,查找操作直接使用偏移地址,鏈表需要按順序檢索結點, 效率低,
-
資料插入或洗掉:鏈表可以快速插入和洗掉結點,而陣列則可能需要大量資料移動,
-
越界問題:鏈表不存在越界問題,陣列有越界問題,
「注意」:在選擇陣列或鏈表資料結構時,一定要根據實際需要進行選擇,陣列便于查詢,鏈表便于插 入洗掉,陣列節省空間但是長度固定,鏈表雖然變長但是占了更多的存盤空間,
48 用兩個堆疊實作一個佇列的功能
typedef struct node
{
int data;
node *next;
}node,*LinkStack;
//創建空堆疊:
LinkStack CreateNULLStack( LinkStack &S)
{
S = (LinkStack)malloc( sizeof( node ) ); // 申請新結點
if( NULL == S)
{
printf("Fail to malloc a new node.\n");
return NULL;
}
S->data = 0; //初始化新結點
S->next = NULL;
return S;
}
//堆疊的插入函式:
LinkStack Push( LinkStack &S, int data)
{
if( NULL == S) //檢驗堆疊
{
printf("There no node in stack!");
return NULL;
}
LinkStack p = NULL;
p = (LinkStack)malloc( sizeof( node ) ); // 申請新結點
if( NULL == p)
{
printf("Fail to malloc a new node.\n");
return S;
}
if( NULL == S->next)
{
p->next = NULL;
}
else
{
p->next = S->next;
}
p->data = data; //初始化新結點
S->next = p; //插入新結點
return S;
}
//出堆疊函式:
node Pop( LinkStack &S)
{
node temp;
temp.data = 0;
temp.next = NULL;
if( NULL == S) //檢驗堆疊
{
printf("There no node in stack!");
return temp;
}
temp = *S;
if( S->next == NULL )
{
printf("The stack is NULL,can't pop!\n");
return temp;
}
LinkStack p = S ->next; //節點出堆疊
S->next = S->next->next;
temp = *p;
free( p );
p = NULL;
return temp;
}
//雙堆疊實作佇列的入隊函式:
LinkStack StackToQueuPush( LinkStack &S, int data)
{
node n;
LinkStack S1 = NULL;
CreateNULLStack( S1 ); //創建空堆疊
while( NULL != S->next ) //S 出堆疊入S1
{
n = Pop( S );
Push( S1, n.data );
}
Push( S1, data ); //新結點入堆疊
while( NULL != S1->next ) //S1 出堆疊入S
{
n = Pop( S1 );
Push( S, n.data );
}
return S;
}
「注意」:用兩個堆疊能夠實作一個佇列的功能,那用兩個佇列能否實作一個佇列的功能呢?結果是否定 的,因為堆疊是先進后出,將兩個堆疊連在一起,就是先進先出,而佇列是現先進先出,無論多少個連在一 起都是先進先出,而無法實作先進后出,
49 模板函式和模板類的特例化
「引入原因」
撰寫單一的模板,它能適應多種型別的需求,使每種型別都具有相同的功能,但對于某種特定型別,如果要實作其特有的功能,單一模板就無法做到,這時就需要模板特例化
「定義」對單一模板提供的一個特殊實體,它將一個或多個模板引數系結到特定的型別或值上
(1)模板函式特例化
必須為原函式模板的每個模板引數都提供實參,且使用關鍵字template后跟一個空尖括號對<>,表明將原模板的所有模板引數提供實參,舉例如下:
template<typename T> //模板函式
int compare(const T &v1,const T &v2)
{
if(v1 > v2) return -1;
if(v2 > v1) return 1;
return 0;
}
//模板特例化,滿足針對字串特定的比較,要提供所有實參,這里只有一個T
template<>
int compare(const char* const &v1,const char* const &v2)
{
return strcmp(p1,p2);
}
「本質」特例化的本質是實體化一個模板,而非多載它,特例化不影響引數匹配,引數匹配都以最佳匹配為原則,例如,此處如果是compare(3,5),則呼叫普通的模板,若為compare(“hi”,”haha”)則呼叫特例化版本(因為這個cosnt char*相對于T,更匹配實參型別),注意二者函式體的陳述句不一樣了,實作不同功能,
「注意」模板及其特例化版本應該宣告在同一個頭檔案中,且所有同名模板的宣告應該放在前面,后面放特例化版本,
(2)類模板特例化
原理類似函式模板,不過在類中,我們可以對模板進行特例化,也可以對類進行部分特例化,對類進行特例化時,仍然用template<>表示是一個特例化版本,例如:
template<>
class hash<sales_data>
{
size_t operator()(sales_data& s);
//里面所有T都換成特例化型別版本sales_data
//按照最佳匹配原則,若T != sales_data,就用普通類模板,否則,就使用含有特定功能的特例化版本,
};
「類模板的部分特例化」
不必為所有模板引數提供實參,可以指定一部分而非所有模板引數,一個類模板的部分特例化本身仍是一個模板,使用它時還必須為其特例化版本中未指定的模板引數提供實參(特例化時類名一定要和原來的模板相同,只是引數型別不同,按最佳匹配原則,哪個最匹配,就用相應的模板)
「特例化類中的部分成員」
可以特例化類中的部分成員函式而不是整個類,舉個例子:
template<typename T>
class Foo
{
void Bar();
void Barst(T a)();
};
template<>
void Foo<int>::Bar()
{
//進行int型別的特例化處理
cout << "我是int型特例化" << endl;
}
Foo<string> fs;
Foo<int> fi;//使用特例化
fs.Bar();//使用的是普通模板,即Foo<string>::Bar()
fi.Bar();//特例化版本,執行Foo<int>::Bar()
//Foo<string>::Bar()和Foo<int>::Bar()功能不同
50 為什么解構式一般寫成虛函式
由于類的多型性,基類指標可以指向派生類的物件,如果洗掉該基類的指標,就會呼叫該指標指向的派生類解構式,而派生類的解構式又自動呼叫基類的解構式,這樣整個派生類的物件完全被釋放,如果解構式不被宣告成虛函式,則編譯器實施靜態系結,在洗掉基類指標時,只會呼叫基類的解構式而不呼叫派生類解構式,這樣就會造成派生類物件析構不完全,造成記憶體泄漏,所以將解構式宣告為虛函式是十分必要的,在實作多型時,當用基類操作派生類,在析構時防止只析構基類而不析構派生類的狀況發生,要將基類的解構式宣告為虛函式,舉個例子:
#include <iostream>
using namespace std;
class Parent{
public:
Parent(){
cout << "Parent construct function" << endl;
};
~Parent(){
cout << "Parent destructor function" <<endl;
}
};
class Son : public Parent{
public:
Son(){
cout << "Son construct function" << endl;
};
~Son(){
cout << "Son destructor function" <<endl;
}
};
int main()
{
Parent* p = new Son();
delete p;
p = NULL;
return 0;
}
//運行結果:
//Parent construct function
//Son construct function
//Parent destructor function
將基類的解構式宣告為虛函式:
#include <iostream>
using namespace std;
class Parent{
public:
Parent(){
cout << "Parent construct function" << endl;
};
virtual ~Parent(){
cout << "Parent destructor function" <<endl;
}
};
class Son : public Parent{
public:
Son(){
cout << "Son construct function" << endl;
};
~Son(){
cout << "Son destructor function" <<endl;
}
};
int main()
{
Parent* p = new Son();
delete p;
p = NULL;
return 0;
}
//運行結果:
//Parent construct function
//Son construct function
//Son destructor function
//Parent destructor function
51 vector的底層原理
vector底層是一個動態陣列,包含三個迭代器,start和finish之間是已經被使用的空間范圍,end_of_storage是整塊連續空間包括備用空間的尾部,
當空間不夠裝下資料(vec.push_back(val))時,會自動申請另一片更大的空間(1.5倍或者2倍),然后把原來的資料拷貝到新的記憶體空間,接著釋放原來的那片空間[vector記憶體增長機制],
當釋放或者洗掉(vec.clear())里面的資料時,其存盤空間不釋放,僅僅是清空了里面的資料,因此,對vector的任何操作一旦引起了空間的重新配置,指向原vector的所有迭代器會都失效了,
52 vector中的reserve和resize的區別
-
reserve是直接擴充到已經確定的大小,可以減少多次開辟、釋放空間的問題(優化push_back),就可以提高效率,其次還可以減少多次要拷貝資料的問題,reserve只是保證vector中的空間大小(capacity)最少達到引數所指定的大小n,reserve()只有一個引數,
-
resize()可以改變有效空間的大小,也有改變默認值的功能,capacity的大小也會隨著改變,resize()可以有多個引數,
53 vector中的size和capacity的區別
-
size表示當前vector中有多少個元素(finish - start);
-
capacity函式則表示它已經分配的記憶體中可以容納多少元素(end_of_storage - start);
54 vector中erase方法與algorithn中的remove方法區別
-
vector中erase方法真正洗掉了元素,迭代器不能訪問了
-
remove只是簡單地將元素移到了容器的最后面,迭代器還是可以訪問到,因為algorithm通過迭代器進行操作,不知道容器的內部結構,所以無法進行真正的洗掉,
55 vector迭代器失效的情況
-
當插入一個元素到vector中,由于引起了記憶體重新分配,所以指向原記憶體的迭代器全部失效,
-
當洗掉容器中一個元素后,該迭代器所指向的元素已經被洗掉,那么也造成迭代器失效,erase方法會回傳下一個有效的迭代器,所以當我們要洗掉某個元素時,需要it=vec.erase(it);,
56 正確釋放vector的記憶體(clear(), swap(), shrink_to_fit())
-
vec.clear():清空內容,但是不釋放記憶體,
-
vector().swap(vec):清空內容,且釋放記憶體,想得到一個全新的vector,
-
vec.shrink_to_fit():請求容器降低其capacity和size匹配,
-
vec.clear();vec.shrink_to_fit();:清空內容,且釋放記憶體,
57 list的底層原理
-
ist的底層是一個雙向鏈表,使用鏈表存盤資料,并不會將它們存盤到一整塊連續的記憶體空間中,恰恰相反,各元素占用的存盤空間(又稱為節點)是獨立的、分散的,它們之間的線性關系通過指標來維持,每次插入或洗掉一個元素,就配置或釋放一個元素空間,
-
list不支持隨機存取,如果需要大量的插入和洗掉,而不關心隨即存取
58 什么情況下用vector,什么情況下用list,什么情況下用deque
-
vector可以隨機存盤元素(即可以通過公式直接計算出元素地址,而不需要挨個查找),但在非尾部插入洗掉資料時,效率很低,適合物件簡單,物件數量變化不大,隨機訪問頻繁,除非必要,我們盡可能選擇使用vector而非deque,因為deque的迭代器比vector迭代器復雜很多,
-
list不支持隨機存盤,適用于物件大,物件數量變化頻繁,插入和洗掉頻繁,比如寫多讀少的場景,
-
需要從首尾兩端進行插入或洗掉操作的時候需要選擇deque,
59 priority_queue的底層原理
priority_queue:優先佇列,其底層是用堆來實作的,在優先佇列中,隊首元素一定是當前佇列中優先級最高的那一個,
60 map 、set、multiset、multimap的底層原理
map 、set、multiset、multimap的底層實作都是紅黑樹,epoll模型的底層資料結構也是紅黑樹,linux系統中CFS行程調度演算法,也用到紅黑樹,
紅黑樹的特性:
-
每個結點或是紅色或是黑色;
-
根結點是黑色;
-
每個葉結點是黑的;
-
如果一個結點是紅的,則它的兩個兒子均是黑色;
-
每個結點到其子孫結點的所有路徑上包含相同數目的黑色結點,
61 為何map和set的插入洗掉效率比其他序列容器高
因為不需要記憶體拷貝和記憶體移動
62 為何map和set每次Insert之后,以前保存的iterator不會失效?
因為插入操作只是結點指標換來換去,結點記憶體沒有改變,而iterator就像指向結點的指標,記憶體沒變,指向記憶體的指標也不會變,
63 當資料元素增多時(從10000到20000),map的set的查找速度會怎樣變化?
RB-TREE用二分查找法,時間復雜度為logn,所以從10000增到20000時,查找次數從log10000=14次到log20000=15次,多了1次而已,
64 map 、set、multiset、multimap的特點
-
set和multiset會根據特定的排序準則自動將元素排序,set中元素不允許重復,multiset可以重復,
-
map和multimap將key和value組成的pair作為元素,根據key的排序準則自動將元素排序(因為紅黑樹也是二叉搜索樹,所以map默認是按key排序的),map中元素的key不允許重復,multimap可以重復,
-
map和set的增刪改查速度為都是logn,是比較高效的,
65 為何map和set的插入洗掉效率比其他序列容器高,而且每次insert之后,以前保存的iterator不會失效?
-
存盤的是結點,不需要記憶體拷貝和記憶體移動,
-
插入操作只是結點指標換來換去,結點記憶體沒有改變,而iterator就像指向結點的指標,記憶體沒變,指向記憶體的指標也不會變,
66 為何map和set不能像vector一樣有個reserve函式來預分配資料?
在map和set內部存盤的已經不是元素本身了,而是包含元素的結點,也就是說map內部使用的Alloc并不是map<Key, Data, Compare, Alloc>宣告的時候從引數中傳入的Alloc,
67 set的底層實作實作為什么不用哈希表而使用紅黑樹?
set中元素是經過排序的,紅黑樹也是有序的,哈希是無序的 如果只是單純的查找元素的話,那么肯定要選哈希表了,因為哈希表在的最好查找時間復雜度為O(1),并且如果用到set中那么查找時間復雜度的一直是O(1),因為set中是不允許有元素重復的,而紅黑樹的查找時間復雜度為O(lgn)
68 hash_map與map的區別?什么時候用hash_map,什么時候用map?
-
建構式:hash_map需要hash function和等于函式,而map需要比較函式(大于或小于),
-
存盤結構:hash_map以hashtable為底層,而map以RB-TREE為底層,
-
總的說來,hash_map查找速度比map快,而且查找速度基本和資料量大小無關,屬于常數級別,而map的查找速度是logn級別,但不一定常數就比log小,而且hash_map還有hash function耗時,
-
如果考慮效率,特別當元素達到一定數量級時,用hash_map,
-
考慮記憶體,或者元素數量較少時,用map,
69 迭代器失效的問題
插入操作:
-
對于vector和string,如果容器記憶體被重新分配,iterators,pointers,references失效;如果沒有重新分配,那么插入點之前的iterator有效,插入點之后的iterator失效;
-
對于deque,如果插入點位于除front和back的其它位置,iterators,pointers,references失效;當我們插入元素到front和back時,deque的迭代器失效,但reference和pointers有效;
-
對于list和forward_list,所有的iterator,pointer和refercnce有效,洗掉操作:
-
對于vector和string,洗掉點之前的iterators,pointers,references有效;off-the-end迭代器總是失效的;
-
對于deque,如果洗掉點位于除front和back的其它位置,iterators,pointers,references失效;當我們插入元素到front和back時,off-the-end失效,其他的iterators,pointers,references有效;
-
對于list和forward_list,所有的iterator,pointer和refercnce有效,
-
對于關聯容器map來說,如果某一個元素已經被洗掉,那么其對應的迭代器就失效了,不應該再被使用,否則會導致程式無定義的行為,
70 STL執行緒不安全的情況
-
在對同一個容器進行多執行緒的讀寫、寫操作時;
-
在每次呼叫容器的成員函式期間都要鎖定該容器;
-
在每個容器回傳的迭代器(例如通過呼叫begin或end)的生存期之內都要鎖定該容器;
-
在每個在容器上呼叫的演算法執行期間鎖定該容器,
71 合并有序鏈表
將兩個有序的鏈表合并為一個新鏈表,要求新的鏈表是通過拼接兩個鏈表的節點來生成的,
輸入:1->2->4, 1->3->4
輸出:1->1->2->3->4->4
#include <iostream>
using namespace std;
struct myList {
int val;
myList* next;
myList(int _val) :val(_val), next(nullptr) {}
};
myList* merge(myList* l1, myList* l2) {
if (l1 == nullptr) return l2;
if (l2 == nullptr) return l1;
myList head(0);
myList* node = &head;
while (l1 != nullptr && l2 != nullptr) {
if (l1->val < l2->val) {
node->next = l1;
l1 = l1->next;
}
else {
node->next = l2;
l2 = l2->next;
}
node = node->next;
}
if (l1 == nullptr)
node->next = l2;
if (l2 == nullptr)
node->next = l1;
return head.next;
};
int main(void) {
myList* node0 = new myList(0);
myList* node1 = new myList(1);
myList* node2 = new myList(2);
myList* node3 = new myList(3);
myList* node4 = new myList(1);
myList* node5 = new myList(4);
node0->next = node1;
node1->next = node2;
node2->next = node3;
node3->next = nullptr;
node4->next = node5;
node5->next = nullptr;
auto node = merge(node0, node4);
while (node != nullptr) {
cout << node->val << endl;
node = node->next;
}
return 0;
}
72 反轉鏈表
定義一個函式,輸入一個鏈表的頭節點,反轉該鏈表并輸出反轉后鏈表的頭節點,
輸入: 1->2->3->4->5->NULL
輸出: 5->4->3->2->1->NULL
//頭插法來做,將元素開辟在堆疊上,這樣會避免記憶體泄露
ListNode* ReverseList(ListNode* pHead) {
// 頭插法
if (pHead == nullptr || pHead->next == nullptr) return pHead;
ListNode dummyNode = ListNode(0);
ListNode* pre = &(dummyNode);
pre->next = pHead;
ListNode* cur = pHead->next;
pHead->next = nullptr;
//pre = cur;
ListNode* temp = nullptr;
while (cur != nullptr) {
temp = cur;
cur = cur->next;
temp->next = pre->next;
pre->next = temp;
}
return dummyNode.next;
}
73 寫一個比較大小的模板函式
#include<iostream>
using namespace std;
template<typename type1,typename type2>//函式模板
type1 Max(type1 a,type2 b)
{
return a > b ? a : b;
}
void main()
{
cout<<"Max = "<<Max(5.5,'a')<<endl;
}
74 智能指標出現回圈參考怎么解決?
弱指標用于專門解決shared_ptr回圈參考的問題,weak_ptr不會修改參考計數,即其存在與否并不影響物件的參考計數器,回圈參考就是:兩個物件互相使用一個shared_ptr成員變數指向對方,弱參考并不對物件的記憶體進行管理,在功能上類似于普通指標,然而一個比較大的區別是,弱參考能檢測到所管理的物件是否已經被釋放,從而避免訪問非法記憶體,
最近發現的一個寶藏資源分享給大家,點擊領取
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/281690.html
標籤:其他
上一篇:資料結構復習筆記————堆
