第4章 第3節 c++

● .請你說一下數組和指針的區別

參考回答:

數組:數組是用於儲存多個相同類型數據的集合。

指針:指針相當於一個變量,但是它和不同變量不一樣,它存放的是其它變量在內存中的地址。

區別:

•    賦值:同類型指針變量可以相互賦值,數組不行,只能一個一個元素的賦值或拷貝

•    存儲方式:數組:數組在內存中是連續存放的,開闢一塊連續的內存空間。數組是根據數組的下進行訪問的,多維數組在內存中是按照一維數組存儲的,只是在邏輯上是多維的。指針:指針很靈活,它可以指向任意類型的數據。指針的類型說明了它所指向地址空間的內存。

•    求sizeof:數組所佔存儲空間的內存:sizeof(數組名),數組的大小:sizeof(數組名)/sizeof(數據類型)。在32位平臺下,無論指針的類型是什麼,sizeof(指針名)都是4,在64位平臺下,無論指針的類型是什麼,sizeof(指針名)都是8。

•    初始化方式不同。

•    傳參方式:數組傳參時,會退化爲指針,C語言將數組的傳參進行了退化。將整個數組拷貝一份傳入函數時,將數組名看做常量指針,傳數組首元素的地址。一級指針傳參可以接受的參數類型:(1)可以是一個整形指針 (2)可以是整型變量地址 (3)可以是一維整型數組數組名;當函數參數部分是二級指針時,可以接受的參數類型:(1)二級指針變量(2)一級指針變量地址(3)一維指針數組的數組名

● 請你說一說STL常用的容器

參考回答:

(1)vector

vector是一種動態數組,在內存中具有連續的存儲空間,支持快速隨機訪問。由於具有連續的存儲空間,所以在插入和刪除操作方面,效率比較慢。vector有多個構造函數,默認的構造函數是構造一個初始長度爲0的內存空間,且分配的內存空間是以2的倍數動態增長的,即內存空間增長是按照20,21,22,23.....增長的,在push_back的過程中,若發現分配的內存空間不足,則重新分配一段連續的內存空間,其大小是現在連續空間的2倍,再將原先空間中的元素複製到新的空間中,性能消耗比較大,尤其是當元素是非內部數據時(非內部數據往往構造及拷貝構造函數相當複雜)。

API:

vector <T> v;//採用模板實現類實現,默認構造函數

assign(begin(),end());//將【begin(),end()】區間中的元素拷貝給本身

size();//返回元素容器中元素個數

at(int idx);//返回索引idx所指的數據,如果idx越界,拋出out_of_range異常

(2)deque

deque和vector類似,支持快速隨機訪問。二者最大的區別在於,vector只能在末端插入數據,而deque支持雙端插入數據。deque的內存空間分佈是小片的連續,小片間用鏈表相連,實際上內部有一個map的指針。deque空間的重新分配要比vector快,重新分配空間後,原有的元素是不需要拷貝的。

API:

deque<T>deqT;//默認構造形式

assign(begin,end);//將【begin,end】區間的數據拷貝賦值給自身

deque.size();//返回容器中元素的個數

push_back(elem);//在容器尾部添加一個數據

(3)list

list是一個雙向鏈表,因此它的內存空間是可以不連續的,通過指針來進行數據的訪問,這使list的隨機存儲變得非常低效,因此list沒有提供[]操作符的重載。但list可以很好地支持任意地方的插入和刪除,只需移動相應的指針即可。

API:

list<T>lstT;//list採用模板類實現對象的默認構造函數

push_back(elem);//在容器尾部加入一個元素

size();//返回元素容器中元素個數

empty();//判斷容器是否爲空

(4)map

map是一種關聯容器,該容器用唯一的關鍵字來映射相應的值,即具有key-value功能。map內部自建一棵紅黑樹(一種自平衡二叉樹),這棵樹具有數據自動排序的功能,所以在map內部所有的數據都是有序的,以二叉樹的形式進行組織。

API:

map<t1,T2>mapT;//map默認構造函數

size();//返回元素中元素的數目

empty();//判斷容器是否爲空

clear();//刪除所有元素

(5)set

set也是一種關聯性容器,它同map一樣,底層使用紅黑樹實現,插入刪除操作時僅僅移動指針即可,不涉及內存的移動和拷貝,所以效率比較高。set中的元素都是唯一的,而且默認情況下會對元素進行升序排列。所以在set中,不能直接改變元素值,因爲那樣會打亂原本正確的順序,要改變元素值必須先刪除舊元素,再插入新元素。不提供直接存取元素的任何操作函數,只能通過迭代器進行間接存取。

API:

set<T>sT;//set默認構造函數

size();//返回容器中元素的數目

empty();//判斷容器是否爲空

insert(elem);//在容器中插入元素

clear();//清除所有元素

(6)queue

queue是一個隊列,實現先進先出功能,queue不是標準的STL容器,卻以標準的STL容器爲基礎。queue是在deque的基礎上封裝的。之所以選擇deque而不選擇vector是因爲deque在刪除元素的時候釋放空間,同時在重新申請空間的時候無需拷貝所有元素。

API:

queue<T>queT;//queue採用模板類實現,queue對象的默認構造形式

push(elem);//往隊尾添加元素

(7)stack

stack是實現先進後出的功能,和queue一樣,也是內部封裝了deque,這也是爲啥稱爲容器適配器的原因吧(純屬猜測)。自己不直接維護被控序列的模板類,而是它存儲的容器對象來爲它實現所有的功能。stack的源代碼原理和實現方式均跟queue相同。

API:

stack<T>stkT;//採用模板類實現,stack對象的默認構造形式

push(elem);//向棧頂添加元素

● 請你說一下虛函數

參考回答:

擁有Virtual 關鍵字的函數稱之爲虛函數,虛函數的作用是實現動態綁定的,也就是說程序在運行的時候動態的的選擇合適的成員函數。要成爲虛函數必須滿足兩點,一就是這個函數依賴於對象調用,因爲虛函數就是依賴於對象調用,因爲虛函數是存在於虛函數表中,有一個虛函數指針指向這個虛表,所以要調用虛函數,必須通過虛函數指針,而虛函數指針是存在於對象中的。二就是這個函數必須可以取地址,因爲我們的虛函數表中存放的是虛函數函數入口地址,如果函數不能尋址,就不能成爲虛函數。

● 請你說一下動態內存分配

參考回答:

在程序執行的過程中動態地分配或者回收存儲空間的分配內存的方法。動態內存分配不象數組等靜態內存分配方法那樣需要預先分配存儲空間,而是由系統根據程序的需要即時分配,且分配的大小就是程序要求的大小。

● 請你說一下深copy淺copy

參考回答:

淺拷貝是增加了一個指針,指向原來已經存在的內存。而深拷貝是增加了一個指針,並新開闢了一塊空間讓指針指向這塊新開闢的空間。淺拷貝在多個對象指向一塊空間的時候,釋放一個空間會導致其他對象所使用的空間也被釋放了,再次釋放便會出現錯誤。

● 請你說一下C 中申請和釋放內存的方法

參考回答:

C++:

new運算符申請內存:

將調用相應的operator new(size_t) 函數動態分配內存,在分配到的動態內存塊上 初始化 相應類型的對象(構造函數)並返回其首地址。如果調用構造函數初始化對象時拋出異常,則自動調用 operator delete(void*, void*) 函數釋放已經分配到的內存。

delete運算符釋放內存:

調用相應類型的析構函數,處理類內部可能涉及的資源釋放,調用相應的operator delete(void *) 函數。

C:

內存區域可以分爲棧,堆,靜態存儲區和常量存儲區。局部變量,函數形參,臨時變量都是在棧上獲得內存的,它們獲取的方式都是由編譯器自動執行的。

而C標準函數庫提供了許多函數來實現對堆上內存管理,其中包括:malloc函數,free函數,calloc函數和realloc函數。使用這些函數需要包含頭文件stdlib.h。

(1)malloc函數

malloc函數可以從堆上獲得指定字節的內存空間,其函數聲明如下:

void * malloc(int n);

其中,形參n爲要求分配的字節數。如果函數執行成功,malloc返回獲得內存空間的首地址;如果函數執行失敗,那麼返回值爲NULL。由於malloc函數值的類型爲void型指針,因此,可以將其值類型轉換後賦給任意類型指針,這樣就可以通過操作該類型指針來操作從堆上獲得的內存空間。需要注意的是,malloc函數分配得到的內存空間是未初始化的。因此,一般在使用該內存空間時,要調用另一個函數memset來將其初始化爲全0。memset函數的聲明如下:

void * memset (void * p,int c,int n) ;

該函數可以將指定的內存空間按字節單位置爲指定的字符c。其中,p爲要清零的內存空間的首地址,c爲要設定的值,n爲被操作的內存空間的字節長度。

(2)free函數

從堆上獲得的內存空間在程序結束以後,系統不會將其自動釋放,需要程序員來自己管理。一個程序結束時,必須保證所有從堆上獲得的內存空間已被安全釋放,否則,會導致內存泄露。

void free (void * p);

由於形參爲void指針,free函數可以接受任意類型的指針實參。

但是,free函數只是釋放指針指向的內容,而該指針仍然指向原來指向的地方,此時,指針爲野指針,如果此時操作該指針會導致不可預期的錯誤。安全做法是:在使用free函數釋放指針指向的空間之後,將指針的值置爲NULL。

(3)calloc函數

calloc函數的功能與malloc函數的功能相似,都是從堆分配內存。其函數聲明如下:

void *calloc(int n,int size);

函數返回值爲void型指針。如果執行成功,函數從堆上獲得size X n的字節空間,並返回該空間的首地址。如果執行失敗,函數返回NULL。該函數與malloc函數的一個顯著不同時是,**calloc函數得到的內存空間是經過初始化的,其內容全爲0。**calloc函數適合爲數組申請空間,可以將size設置爲數組元素的空間長度,將n設置爲數組的容量。

(4)realloc函數

realloc函數的功能比malloc函數和calloc函數的功能更爲豐富,可以實現內存分配和內存釋放的功能,其函數聲明如下:

void * realloc(void * p,int n);

其中,指針p必須爲指向堆內存空間的指針,即由malloc函數、calloc函數或realloc函數分配空間的指針。realloc函數將指針p指向的內存塊的大小改變爲n字節。如果n小於或等於p之前指向的空間大小,那麼。保持原有狀態不變。如果n大於原來p之前指向的空間大小,那麼,系統將重新爲p從堆上分配一塊大小爲n的內存空間,同時,將原來指向空間的內容依次複製到新的內存空間上,p之前指向的空間被釋放。relloc函數分配的空間也是未初始化的。

● 請你說一說C++和C的區別

參考回答:

•    C是面向過程的語言,而C++是面向對象的語言

•    C和C++動態管理內存的方法不一樣,C是使用malloc/free函數,而C++除此之外還有new/delete關鍵字

•    C中的struct和C++的類,C++的類是C所沒有的,但是C中的struct是可以在C++中正常使用的,並且C++對struct進行了進一步的擴展,使struct在C++中可以和class一樣當做類使用,而唯一和class不同的地方在於struct的成員默認訪問修飾符是public,而class默認的是private;

•    C++支持函數重載,而C不支持函數重載,而C++支持重載的依仗就在於C++的名字修飾與C不同,例如在C++中函數int fun(int ,int)經過名字修飾之後變爲 _fun_int_int ,而C是 _fun,一般是這樣的,所以C++纔會支持不同的參數調用不同的函數;

•    C++中有引用,而C沒有;

•    C++全部變量的默認鏈接屬性是外鏈接,而C是內連接;

•    C 中用const修飾的變量不可以用在定義數組時的大小,但是C++用const修飾的變量可以

● 請你回答一下C++中的多態是怎麼實現的

參考回答:

C++的多態性是用虛函數和延遲綁定來實現的,在基類的函數前加上virtual關鍵字,在派生類中重寫該函數,運行時將會根據對象的實際類型來調用相應的函數。如果對象類型是派生類,就調用派生類的函數;如果對象類型是基類,就調用基類的函數。

1. 用virtual關鍵字申明的函數叫做虛函數,虛函數肯定是類的成員函數。

2. 存在虛函數的類都有一個一維的虛函數表叫做虛表。類的對象有一個指向虛表開始的虛指針。虛表是和類對應的,虛表指針是和對象對應的。

3. 多態性是一個接口多種實現,是面向對象的核心。分爲類的多態性和函數的多態性。

4. 多態用虛函數來實現,結合動態綁定。

5. 純虛函數是虛函數再加上= 0。

6. 抽象類是指包括至少一個純虛函數的類。

● 請你說一下C語言的內存分配

參考回答:

在C語言中,對象可以使用靜態或動態的方式分配內存空間

靜態分配:編譯器在處理程序源代碼時分配,由於是在程序執行之前進行所以效率比較高

動態分配:程序在執行時調用malloc庫函數申請分配,可以靈活的處理未知數目的對象

靜態與動態內存分配的主要區別如下:

靜態對象是有名字的變量,可以直接對其進行操作;動態對象是沒有名字的變量,需要通過指針間接地對它進行操作。

靜態對象的分配與釋放由編譯器自動處理;動態對象的分配與釋放必須由程序員顯式地管理,它通過malloc()和free兩個函數(C++中爲new和delete運算符)來完成。

● 請你回答一下什麼是指針,以及指針和數組的區別,指針和引用的區別

參考回答:

指針就是一個存放地址的變量,當指針指向某個變量,這時這個指針裏就存放了那個變量的地址

指針與數組的區別

1、指針和數組的分配,數組是開闢一塊連續的內存空間,指針不是

2、空間分配,   這裏又分爲兩種情況。
第一,如果是全局的和靜態的
char *p = “hello”;
這是定義了一個指針,指向rodata section裏面的“hello”,可以被編譯器放到字符串池。在彙編裏面的關鍵字爲.ltorg。意思就是在字符串池裏的字符串是可以共享的,這也是 編譯器優化的一個措施。
char a[] = “hello”;
這是定義了一個數組,分配在可寫數據塊,不會被放到字符串池。
第二,如果是局部的
char *p = “hello”;
這是定義了一個指針,指向rodata section裏面的“hello”,可以被編譯器放到字符串池。在彙編裏面的關鍵字爲.ltorg。意思就是在字符串池裏的字符串是可以共享的,這也是 編譯器優化的一個措施。另外,在函數中可以返回它的地址,也就是說,指針是局部變量,但是它指向的內容是全局的。
char a[] = “hello”;
這是定義了一個數組,分配在堆棧上,初始化由編譯器進行。(短的時候直接用指令填充,長的時候就從全局字符串表拷貝),不會被放到字符串池(同樣如前,可 能會從字符串池中拷貝過來)。注意不應該返回它的地址。









3、使用方法

如果是全局指針,用於不需要修改內容,但是可能會修改指針的情況。如果是全局數組,用於不需要修改地址,但是卻需要修改內容的情況。如果既需要修改指針,又需要修改內容,那麼就定義一個數組,再定義一個指針指向它就可以了。

指針和引用的區別

1、指針是一個變量,只不過這個變量存儲的是一個地質,而引用跟原來的變量實質上是一個東西,只不過是原變量的一個別名

2、引用不可以爲空,當被創建的時候,必須初始化,而指針可以是空

3、指針可以有多級,但是引用只有一級

4、指針的值在初始化後可以改變,但是引用進行初始化後就不會再改變了

5、sizeof引用得到的是指向變量的大小,而指針得到的是本身的大小

6、如果返回動態分配的對象或內存,必須使用指針,否則可能引起內存泄漏

● 請你說一下const和指針的區別,以及運算符優先級是怎麼樣的

參考回答:

const和指針的區別

以下三個語法的區別

(1) const int *p1;

(2) int const *p2;

(3) int *const p3;

由於指針*p的賦值方式有兩種
第一種是:p = &a;
第二種是:*p = a;

(1)和(2)的效果是一樣的。只能通過第一種方式來修改指針指向的變量

而(3)的方式是隻能在一開始的時候指定一個變量,以後不能再指向其他變量。

其實主要是看const後面的變量是什麼,只有const後面的變量無法修改

運算符優先級,簡單來說就是!>算術運算符>關係運算符>&&>||>賦值運算符

● 手寫代碼:寫一個程序算出100以內7的倍數

參考回答:

複製代碼

1

2

3

4

5

6

7

8

9

10

11

void beishu()

{

 int i ;

 i=7;

 while(i<=100)

 {

 if((i%7)==0)

 printf("%d",i);

 i+=7;

 }

 }


● 手寫代碼:寫一個函數,不用加法和乘法,返回他的八倍

參考回答:

複製代碼

1

2

3

4

5

6

7

8

9

10

11

12

13

import org.junit.Test;

public class solution {

 @Test

 public void testFunc(){

 int i = 1;

 int res = beiShu(i);

 System.out.println("res: "+res);

 }

 public int beiShu(int x){

 return x<<3;

 }

 

 }

● 請你說一下new和malloc的區別

參考回答:

1、屬性:new是C++關鍵字,需要編譯器支持,malloc是庫函數,需要頭文件支持

2、參數:使用new操作符申請內存分配時無需指定內存塊的大小,編譯器會根據類型信息自行計算,malloc則需要顯式的指出所需內存的尺寸

3、返回類型:new返回的是對象類型的指針,malloc返回void*,需要通過強制類型轉換將void*指針轉換成我們需要的類型

4、分配失敗:new內存分配失敗時,會跑出bac_alloc異常,malloc分配內存失敗返回null

5、 自定義類型

new會先調用operator new函數,申請足夠的內存(通常底層使用malloc實現)。然後調用類型的構造函數,初始化成員變量,最後返回自定義類型指針。delete先調用析構函數,然後調用operator delete函數釋放內存(通常底層使用free實現)。

malloc/free是庫函數,只能動態的申請和釋放內存,無法強制要求其做自定義類型對象構造和析構工作。

6、 重載

C++允許重載new/delete操作符,特別的,佈局new的就不需要爲對象分配內存,而是指定了一個地址作爲內存起始區域,new在這段內存上爲對象調用構造函數完成初始化工作,並返回此地址。而malloc不允許重載。

7、內存區域

new操作符從自由存儲區(free store)上爲對象動態分配內存空間,而malloc函數從堆上動態分配內存。自由存儲區是C++基於new操作符的一個抽象概念,凡是通過new操作符進行內存申請,該內存即爲自由存儲區。而堆是操作系統中的術語,是操作系統所維護的一塊特殊內存,用於程序的內存動態分配,C語言使用malloc從堆上分配內存,使用free釋放已分配的對應內存。自由存儲區不等於堆,如上所述,佈局new就可以不位於堆中。

● 請你說一說C++語言的三大特性

參考回答:

封裝,繼承,多態

● 請你說一說虛函數和純虛函數區別

參考回答:

虛函數和純虛函數區別

觀點一:類裏聲明爲虛函數的話,這個函數是實現的,哪怕是空實現,它的作用就是爲了能讓這個函數在它的子類裏面可以被重載,這樣的話,這樣編譯器就可以使用後期綁定來達到多態了
純虛函數只是一個接口,是個函數的聲明而已,它要留到子類裏去實現。
class A{
protected:
void foo();//普通類函數
virtual void foo1();//虛函數
virtual void foo2() = 0;//純虛函數
}
觀點二:虛函數在子類裏面也可以不重載的;但純虛必須在子類去實現,這就像Java的接口一樣。通常我們把很多函數加上virtual,是一個好的習慣,雖然犧牲了一些性能,但是增加了面向對象的多態性,因爲你很難預料到父類裏面的這個函數不在子類裏面不去修改它的實現







觀點三:虛函數的類用於“實作繼承”,繼承接口的同時也繼承了父類的實現。當然我們也可以完成自己的實現。純虛函數的類用於“介面繼承”,主要用於通信協議方面。關注的是接口的統一性,實現由子類完成。一般來說,介面類中只有純虛函數的。
觀點四:帶純虛函數的類叫虛基類,這種基類不能直接生成對象,而只有被繼承,並重寫其虛函數後,才能使用。這樣的類也叫抽象類。
虛函數是爲了繼承接口和默認行爲
純虛函數只是繼承接口,行爲必須重新定義


● 請你說一下static作用

參考回答:

Static作用:

1、隱藏,當同時編譯多個文件時,所有未加static的全局變量和函數都具有全局可見性。

2、保持變量內容的持久,存儲在靜態數據區的變量會在程序放開是運行時就完成初始化,也是唯一一次初始化,共有兩種變量存儲在靜態存儲區,全局變量和static變量,PS:如果static局部變量在函數內定義,他的生存期爲整個源程序,但其作用域和自動變量相同,只能在定義該變量的函數內使用,退出該函數後,儘管該變量還存在,但是不能使用。

3、默認初始化爲0,

● 請問你怎麼理解多態,他有什麼好處

參考回答:

所謂多態,就是指程序中定義的引用變量所指向的具體類型和通過該引用變量發出的方法調用在編程時並不確定,而是在程序運行時確定,即一個引用變量到底會指向哪個類的實例對象,調用哪個類的實現方法,由程序運行期間才確定,這樣不用修改程序源代碼就可以讓引用變量綁定到各種不同的類實現上,從而導致該引用調用的具體方法隨之改變,即不修改程序代碼就可以改變程序運行時所綁定的具體代碼,讓程序可以選擇多個運行狀態,這就是多態性

● 手寫代碼:求兩個數的最大公約數

參考回答:

複製代碼

1

2

3

int division(int n,int m)

{

if(n<m)

division考(m,n); //交換m與n

else if(m==0)
return n;
else
{
int temp=n;
n=m;
m=temp%n;





division(n,m); //重複上述過程

複製代碼

1

2

}

}


● 手寫代碼:將字符串轉int類型,要求不能用已有的方法

參考回答:

複製代碼

1

2

3

4

5

6

7

8

9

public static int stringToInt(String str) {

int result=0;

char[] ch=str.toCharArray();

int len=ch.length;

for(int i=0;i<len;i++) {

result+=(((int)ch[i]-'0')*Math.pow(10, len-1-i));

}

return result;

}


● 手寫代碼:求x的n次方

參考回答:

double Pow(double x, int n)
{
double result = 1;
while (n)
{



if (n & 1)        // 等價於 if (n % 2 != 0)

複製代碼

1

result *= x;

n >>= 1;          //右移一位相當於n/2(類比十進制來理解)

複製代碼

1

2

3

4

x *= x;

}

return result;

}


圖片


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章