1.strcpy()
char * strcpy( char *strDest, const char *strSrc )
{
assert( (strDest != NULL) && (strSrc != NULL) );
char *address = strDest;
while( (*strDest++ = * strSrc++) != ‘\0’ );
return address;
}
2.下列代碼有何問題
void GetMemory( char **p, int num )
{
*p = (char *) malloc( num );
}
void Test( void )
{
char *str = NULL;
GetMemory( &str, 100 );
strcpy( str, "hello" );
printf( str );
}
1.*p沒有判斷NULL 的情況
2.沒有free
3.printf沒有字符輸出格式化
void Test( void )
{
char *str = (char *) malloc( 100 );
strcpy( str, "hello" );
free( str );
... //省略的其它語句
}
1.沒有判斷*str=NULL
2.*str釋放以後沒有等於NULL
3.編寫一個函數,作用是把一個char組成的字符串循環右移n個。比如原來是“abcdefghi”如果n=2,移位後應該是“hiabcdefg” 函數頭是這樣的:
//pStr是指向以’\0’結尾的字符串的指針
//steps是要求移動的n
void LoopMove(char *str,int steps){
int len = strlen(str);
char tmp[MAXSIZE];
strcpy(tmp,str+len-steps);
strcpy(tmp+steps,str);
*(tmp+len) = '/0';
strcpy(str,tmp);
}
void LoopMove(char *str,int steps){
int len = strlen(str);
char tmp[MAXSIZE];
memcpy(tmp,str+len-steps,steps);
memcpy(str+steps,str,len-steps);
memcpy(str,tmp,steps);
}
4.編寫string 的構造函數,析構函數和賦值函數
class String{
public:
String(const char *str = NULL);//普通構造函數
String(const String &other);//拷貝構造函數
~String(void);//析構函數
String &operator =(const String &other);//賦值函數
private:
char *m_data;//用於保存字符串
}
//普通構造函數
String::String(const char *str){
if(str == NULL){
m_data = new char[1];
*m_data = '\0';
}else{
int length = strlen(str);
m_data = new char[length+1];
strcpy(m_data,str);
}
}
//普通析構函數
String::~String(void){
delete [] m_data;
}
//拷貝構造函數
String::String(const String &other){
int length = strlen(other.m_data);
m_data = new char[length+1];
strcpy(m_data,other.m_data);
}
//賦值函數
String &String::operator =(const String &other){
if(this == &other)
return *this;
delete [] m_data;
int length = strlen(other.m_data);
m_data = new char[length+1];
strcpy(m_data,other.m_data);
return *this;
}
5.static和const的作用
- Static
(1)函數體內static變量的作用範圍爲該函數體,不同於auto變量,該變量的內存只被分配一次,因此其值在下次調用時仍維持上次的值;
(2) 在模塊內的static全局變量可以被模塊內所用函數訪問,但不能被模塊外其它函數訪問;
(3)在模塊內的static函數只可被這一模塊內的其它函數調用,這個函數的使用範圍被限制在聲明它的模塊內;
(4)在類中的static成員變量屬於整個類所擁有,對類的所有對象只有一份拷貝;
(5)在類中的static成員函數屬於整個類所擁有,這個函數不接收this指針,因而只能訪問類的static成員變量。
- const
(1)欲阻止一個變量被改變,可以使用const關鍵字。在定義該const變量時,通常需要對它進行初始化,因爲以後就沒有機會再去改變它了; >(2)對指針來說,可以指定指針本身爲const,也可以指定指針所指的數據爲const,或二者同時指定爲const;
(3)在一個函數聲明中,const可以修飾形參,表明它是一個輸入參數,在函數內部不能改變其值;
(4)對於類的成員函數,若指定其爲const類型,則表明其是一個常函數,不能修改類的 成員變量;
(5)對於類的成員函數,有時候必須指定其返回值爲const類型,以使得其返回值不爲“左值”
6.怎麼用一個函數,判斷大端還是小段納,如果是小端則返回1,如果是大端則返回0
#include <iostream>
int checkCPU(){
union w{
int a;
char b;
} c;
c.a = 1;
return(c.b == 1);
}
7.C和C++的區別
- 思想上:C++是面向對象的語言,而c是面向過程的結構化編程語言。
- 語法上:C++有封裝,繼承和多態三種特性
C++相比C,增加許多安全功能,比如強制類型轉換
C++支持範式編程,比如模板類,函數模板等
8.c++中四種cast轉換
- const_cast:用於將const變量轉換爲非const變量
- static_cast:用於各種隱式轉換,比如非const轉const,void*轉指針等,static_cast能用於多態向上轉換但是不能向下轉換
- dynamic_cast:動態類型轉換。只能用於含有虛函數的類,用於類層次之間轉換,只能夠轉指針或者引用,非法指針返回NULL,非法引用拋出異常
- reinterpret_cast:什麼都可以轉換
9.指針和引用的區別
- 指針擁有獨立的存儲空間,引用共享存儲空間,引用只是一個別名
- 指針可以初始化爲NULL,而引用則是被引用對象的大小
- sizeof()引用對象的大小,而指針則是地址4/8
- 可以有const指針但是沒有const引用
- 指針可以有多級指針,而引用只能夠是一級
- 指針++和引用++含義不一樣
- 指針可以指向其他對象,而引用則不能夠在改變
- 作爲傳遞參數的時候,指針需要被解引用纔可以對對象進行操作,而直接對引用的修改都會改變引用所指向的對象
- 如果返回動態內存分配的對象或者內存,必須使用指針,引用可能引起內存泄露。
10.c++中四個智能指針的理解:shared_ptr,unique_ptr,weak_ptr,auto_ptr
爲什麼要使用智能指針:智能指針的作用是管理一個指針,因爲有些時候申請的內存空間忘記釋放,造成內存泄露。使用智能指針能夠很大的避免這類事情的發生,因爲智能指針就是一個類,當超出作用域的時候類會自動調用析構函數,自動釋放內存空間,不需要手動釋放空間
- auto_ptr:採用所有權模式
#include <iostream>
unique_ptr<string> p1(new string("auto"));
unique_ptr<string> p2;
p2 = p1;//不會報錯
//p2剝奪p1的運行權,當訪問p1的時候會報錯,auto_ptr的缺點是存在潛在崩潰 的情況
- unique_ptr:保證同一時間內只有一個智能指針可以指向該對象它對於避免資源泄露(例如“以new創建對象後因爲發生異常而忘記調用delete”)特別有用。
#include <iostream>
unique_ptr<string> p1(new string("auto"));
unique_ptr<string> p2;
p2 = p1;//禁止如果強制需要的話就用 p2 = move(p1);
p2 = unique_ptr<string> new string("I love you");
- shared_ptr:實現共享式擁有的概念。多個指針可以指向相同的對象,該對象和相關資源會在最後一個引用被銷燬時釋放。它使用計數機制計算同一個資源被幾個指針共享,通過use_count()來查看資源所有者的個數,處了通過new來構造,還可以通過傳入auto_ptr,unique_ptr,weak_ptr來構造。當我們調用release時,當前指針會釋放資源所有權:
成員函數:
use_count();//返回計數的個數
swap();//交換兩個shared_ptr對象(交換所擁有的對象)
reset();//放棄內部對象的所有權或擁有對象的變更,計數減少
get();//返回內部指針 如:shared_ptr<int>sp(new int(1))此時sp等價sp.get()
- 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\n";
}
};
class B
{
public:
shared_ptr<A> pa_;
~B()
{
cout<<"B delete\n";
}
};
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;
}
11.數組和指針的區別
指針 | 數組 |
---|---|
保存數據的地址 | 保存數據的具體值 |
通常用於動態數據 結構 | 通常用於固定數據數目和數據類型 |
通過malloc分配內存 | 隱式分配和刪除 |
訪問數據,首先獲得指針的內容通過地址訪問數據,效率更低 | 直接訪問數據 |
12.什麼是野指針
野指針就是指向一個已經刪除的對象或者未申請訪問受限區域的指針。
13.爲什麼析構函數必須是虛函數?爲什麼c++默認的析構函數不是虛函數?
- 將可能會被繼承的父類析構函數設置成爲虛函數,可以保證當我們new一個子類,然後使用基類指針指向該對象,釋放基類指針時可以釋放掉子類空間防止內存泄露。
- c++默認析構函數不是虛函數因爲虛函數需要而額外的虛函數表和虛表指針,佔用額外的內存。而對於不會被繼承的類來說,其析構函數如果是虛函數則會浪費內存。因此c++默認的析構函數不是虛函數,而是隻有當需要作爲父類的時候才需要。
14.函數指針,指針函數
- 定義
函數指針是指向函數的指針變量
指針函數是,函數的返回值是指針 - 用途
函數指針:回調函數 - case
char *fun(char *p){...}//函數fun
char *(*pf)(char *p);//函數指針pf
pf = fun ;//函數指針指向fun
pf(p);//通過函數指針調用函數fun
15.map和set的區別
map和set都是c++的關聯容器,其底層實現都是紅黑樹(RB-Tree).由於map和set所開放的各種操作接口,RB-tree也都提供了,所以幾乎所有的map和set的操作行爲都是轉調RB_tree的操作行爲
(1)map的元素是key-value(關鍵字-值)對:關鍵字起到索引的作用,set與之相對的就是關鍵字的簡單的集合,set中的每個元素只包含一個關鍵字
(2)set的迭代器是const的,不允許修改元素的值,map允許修改value,但不允許修改key。其原因是因爲map和set是根據關鍵字排序來保證其有序性的,如果修改key的話,那麼首先要刪除該鍵,然後調節平衡,在插入修改後的鍵值,調節平衡,如此以來嚴重破壞了map和set的結構,導致iterator失效,不知道應該指向改變前的位置,還是指向改變後的位置,所以將STL中將set的迭代器設置成const,不允許修改迭代器的值;而map中的迭代器不允許修改key的值
(3)map具有支持下表的操作,set不支持下標的操作,map可以用key作爲下標,map的下標運算符[]將關鍵碼作爲下標去執行查找,如果關鍵碼不存在則插入。
16.介紹一下STL的allocaotr
STL的分配器用於封裝STL容器在內存管理上的細節。在C++中,其內存配置和釋放如下: new運算分兩個階段:(1)調用::operator
new配置內存(2)調用對象構造函數構造對象內容 delete運算分兩個部分:(1)調用對象析構函數 (2)調用::operator
delete釋放函數 爲了精密分工,STL
allocator將兩個階段操作區分開來:內存配置有alloc::allocate()負責;對象構造由::construct()負責,對象析構由::destroy負責。
同時爲了提升內存管理的效率,減少申請內存造成的內存碎片,SGI
STL採用了兩級配置器,當分配的空間大小超過128g時採用第一級,malloc().realloc(),free()函數進行內存空間的分配和釋放,而第二級空間配置器採用內存池子的技術,通過空閒鏈表管理內存
17.Struct和classs的區別
在C++中,可以用struct和class定義類,都可以繼承。區別在於:structural的默認繼承權限和默認訪問權限是public,而class的默認繼承權限和默認訪問權限是private。
另外,class還可以定義模板類形參,比如template <class T, int i>。
18.如何判斷是否內存泄露
- 使用linux下面的內存泄露檢查工具Valgrind.另外一方面在寫代碼時候可以添加呢村申請和釋放的統計功能,統計當前申請和釋放內存是否一致,以此來判斷是否泄露
19.爲什麼會發生段錯呢?
段錯誤通常發生在訪問非法內存地址的時候:
1.使用野指針
2.試圖修改字符串常量的內容
20.malloc的原理,另外調用brk系統調用和mmap系統調用的作用分別是什麼?
Malloc函數用於動態分佈內存。爲了減少內存碎片和系統調用的開銷,malloc採用內存池的方式,先申請大塊內存作爲堆區,然後將堆區分爲多個小塊,以快爲單位作爲內存管理的基本單位。當用戶申請內存時,直接從堆分配一塊合適的空閒塊。Malloc採用隱式鏈表結構將堆區分成連續的,大小不一的快,包含分配塊和未分配快,同時malloc採用顯式鏈表結構來管理所有的空閒塊,即使用一個雙向鏈表將空閒塊連接起來,每一個塊記錄了一個連續的,未分配的地址。
當進行內存分配的時候,malloc會通過隱式鏈表遍歷所有的空閒塊,選擇滿足要求的塊進行分配,當進行內存合併的時候,malloc會使用邊界標記法,根據每個塊的前後快是否已經分配來決定是否進行塊合併。
採用Malloc在進行申請內存的時候,一般會通過brk或者mmap系統進行調用進行申請。其中當申請的內存少於128K時,會使用系統函數brk在堆中進行分配;當時申請內存大於128K時會使用系統函數mmap在映射區分配。
21.c++中內存管理什麼樣子的
在C++中,虛擬內存分爲代碼段,數據段,BSS,堆區,文件映射區,棧區。
代碼段:包括只讀存儲區和文本存儲區,其中只讀存儲區存儲字符串常量,文本存儲區存儲程序的機器代碼
數據段:存儲已經初始化的全局變量和靜態變量
bss段:存儲未初始化的全局變量和靜態變量(局部+全局),以及所有被初始化爲0的全局變量和靜態變量
堆區:調用new/malloc函數時在堆區動態分配內存,同時需要delete和free手動釋放內存
棧:函數參數局部變量,linux創建進程用ulimit設置
映射區域:動態連接庫以及調用mmap函數進行文件映射
22.解釋一下內存泄漏
- 概念:內存泄露指的是申請的內存不再使用但是沒有釋放。
- 堆內存的泄漏
- 系統資源泄漏:主要指程序使用系統分配的資源比如Bitmap,handle,SOCKET等沒有使用相應的函數釋放掉,導致系統資源的嚴重浪費,嚴重可導致系統效能降低,系統運行不穩定。
- 沒有將基類的析構函數設置成爲虛函數。繼承的子類,沒有調用析構函數因此沒有進行釋放
23.New和Malloc的區別
1.new是一個操作符不可以重載,malloc是一個庫函數
2.new異常拋出bad_malloc,malloc拋出NULL
3.malloc分配要按照指定的大小,而new分配則是指定類型
4.new返回的是指定類型的指,而malloc返回的則是void*,因此malloc一般需要進行類型轉化。
24.c++11的新特性
1)關鍵字以及新語法:auto,nullptr,for
2)STL容器:std::array,std::forward_list,std:unordered_map,std::unordered_set
3)多線程:std::thread,std::atomic,std::consition_variable
4) 智能指針內存管理:std::shared_ptr,std::weak_ptr
5) 其他:lamda表達式