數據庫:
範式
第一範式:數據庫表的每一項都是不可分割的原子數據項,不能是集合。比如班級信息表裏面不能有班級的學生。
第二範式:在第一範式的基礎上,所有屬性完全依賴於主鍵,完全依賴就是不能取決於主鍵的一部分
第一範式->第二範式->第三範式
→→→數據冗餘越來越少,查詢越來越複雜
事務
1原子性
2一致性:
使數據庫從一個一致性狀態到另一個一致性狀態
3隔離性:
一個事物的執行不被其他事務干擾
4永久性:
常用SQL語句
分組查詢(avg max min)、複雜連接查詢、嵌套查詢、結果排序(逆序ansc)、
操作系統
錯題
虛存=min(主存+輔存,邏輯地址)
進程和線程
進程是程序的一次執行,包括代碼和數據,是CPU分配資源的基本單位,一個進程可以包括多個線程。進程之間通信方式:管道、SOCKET、信號量(互斥、同步)等。
進程的調度算法
文件系統
內存分配策略
計算機網絡
HTTP協議
基於TCP協議的應用層協議
HTTP是一個普通用在瀏覽器和web服務器之間進行數據交換的文本協議
HTTP協議的ETAG響應頭主要用於信息的過期驗證
HTML錯誤代碼
(1) 常見錯誤代碼:
200服務器成功返回了網頁,成功處理了請求
304未修改,自從上次請求後,請求的頁面未被修改過,此時服務器不會返回網頁內容,節省帶寬和開銷
404請求的網頁不存在
500服務器內部錯誤
503服務器暫時不可用(超載、停機維護),通常只是暫時狀態
(2) 1xx:臨時響應,服務器端響應成功,等待請求者進一步操作
(3) 2xx:響應成功
202接受請求,未處理
204處理了請求,但沒有返回任何內容
(4) 3xx重定向,要完成響應,服務器需要進一步處理
301網頁已被永久移動到新位置
302臨時移動到新位置
305要求只能使用代理才能訪問
(5) 4xx請求錯誤
400不理解請求語法
401要求身份驗證,先登陸才能請求
403禁止訪問,服務器拒絕請求
405請求中的方法被禁用
408請求超時
(6) 5xx服務器在處理請求時內部發生錯誤,來自服務器本身的錯誤
501服務器不具備完成該請求的功能
502服務器作爲網關或代理,從上游服務器收到無效響應
504網關超時
TCP/IP與UDP
TCP與UDP
TCP面向連接、可靠的數據傳輸,有擁塞控制和流量控制,大量數據,速度慢
TCP建立連接的三次握手
詳細過程和狀態變化
TCP關閉連接的四次揮手
FIN-->
<--FINACK
ßFIN
如何提高UDP的可靠性
應用層實現超時重傳,客戶端發送一個包,服務器返回一個包表示收到,如果客戶端超過一定時間沒有收到該包,就需要重傳
備用
SOCKET編程
當recv函數在接受數據時是阻塞的,當返回值<0,說明連接出錯
當返回值=0,表示對端關閉了連接
返回值>0,接受到的數據的大小
TCP/IP分層,各層作用
應用層:爲操作系統、應用程序提供訪問網絡的接口(Telnet、FTP、HTTP、SNMP、DNS域名解析)
(表示層)
(會話層)
傳輸層:兩點之間的根據使用的協議(TCP、UDP),傳輸相應數據報文,
網絡層:整個網絡的傳輸路徑選擇,路由(IP、RIP)
網絡訪問層:數據鏈路層、物理層的結合,數據鏈路層相鄰節點之間數據幀傳輸,物理層就是光纖上比特級的數據傳輸。
PING操作的原理
使用ICMP,在IP主機、路由器之間傳遞控制消息
網絡層的協議
IP協議 根據IP地址決定轉發、路由的協議
ICMP本質理解爲帶差錯報告的IP協議,在主機和路由器之間傳遞控制信息(網絡通不通,主機可不可達,路由可不可達到)
ARP:將IP地址轉化爲MAC地址
RARP:物理地址轉爲IP地址
傳輸層的協議
TCP
UDP
應用層協議
HTTP
SMTP:簡單郵件傳輸協議
DNS:
Telnet
FTP:文件傳輸協議
WWW:
DNS的完整流程(域名->IP地址)
DNS採用分佈式的域名系統,減少故障發生
當一個應用需要把主機名解析爲IP地址時,該應用程序就會調用解析程序,把待解析的域名放在DNS請求報文中,以UDP數據報方式發送給本地域名服務器,本地服務器在查找域名後,把對應的IP地址放在回答報文中返回,應用程序獲得目的主機的IP地址後即可進行通信。若本地域名服務器不能解析該域名,則向上級域名服務器繼續發送查詢請求,直到可以解析爲止。
備用
HTTP1.0/1.1區別
HTTP1.1中才有cache-control響應頭,主要用於控制信息在瀏覽器的緩存
1. HTTP 1.0規定瀏覽器與服務器只保持短暫的連接,瀏覽器的每次請求都需要與服務器建立一個TCP連接,服務器完成請求處理後立即斷開TCP連接,服務器不跟蹤每個客戶也不記錄過去的請求
缺陷:訪問一個包含有許多圖像的網頁文件的整個過程包含了多次請求和響應,每次請求和響應都需要建立一個單獨的連接,每次連接只是傳輸一個文檔和圖像,器端每次建立和關閉連接卻是一個相對比較費時的過程,並且會嚴重影響客戶機和服務器的性能
2. HTTP 1.1支持持久連接,在一個TCP連接上可以傳送多個HTTP請求和響應,減少了建立和關閉連接的消耗和延遲
HTTP 1.1還允許客戶端不用等待上一次請求結果返回,就可以發出下一次請求,但服務器端必須按照接收到客戶端請求的先後順序依次回送響應結果
HTTP 1.1還提供了Host、身份認證、狀態管理和Cache緩存等機制相關的請求頭和響應頭
編程經驗
C++
一定要弄懂c++的內存分配機制,父類,子類繼承時的內存如何分配,封裝、繼承、多態、虛函數的實現機制和原理。
小細節
實現String類
String類的原型如下
class String
{
public:
String(const char *str=NULL); //構造函數
String(const String &other); //拷貝構造函數
~String(void); //析構函數
String& operator=(const String &other); //等號操作符重載
ShowString();
private:
char *m_data; //指針
};
String::~String()
{
delete [] m_data; //析構函數,釋放地址空間
}
String::String(const char *str)
{
if (str==NULL)//當初始化串不存在的時候,爲m_data申請一個空間存放'\0';
{
m_data=new char[1];
*m_data='\0';
}
else//當初始化串存在的時候,爲m_data申請同樣大小的空間存放該串;
{
int length=strlen(str);
m_data=new char[length+1];
strcpy(m_data,str);
}
}
String::String(const String &other)//拷貝構造函數,功能與構造函數類似。
{
int length=strlen(other.m_data);
m_data=new [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 [length+1];
strcpy(m_data,other.m_data);
return *this;
}
String::ShowString()//由於m_data是私有成員,對象只能通過public成員函數來訪問;
{
cout<<this->m_data<<endl;
}
Strcmp strcpy的返回類型
C++如何限制類對象只能靜態分配或者只能只能動態分配
動態分配就是用運算符new來創建一個類的對象,在堆上分配內存。
(1)動態分配(在堆上分配內存)
將類的構造函數和析構函數設爲protected屬性,這樣類對象不能夠訪問,但是派生類能夠訪問,能夠正常的繼承。同時創建另外兩個create和destory函數類創建對象。(將create設爲static原因是:創建對象的時候是A *p = A::create();只有靜態成員函數才能夠通過類名來訪問。)
[cpp] view plain copy
1. class A
2. {
3. protected:
4. A(){}
5. ~A(){}
6. public:
7. static A* create()
8. {
9. return new A();
10. }
11. void destory()
12. {
13. delete this;
14. }
15. };
(2)靜態分配(在棧上)
把new、delete運算符重載爲private屬性就可以了。
[cpp] view plain copy
1. class A
2. {
3. private:
4. void* operator new(size_t t){} // 注意函數的第一個參數和返回值都是固定的
5. void operator delete(void* ptr){} // 重載了new就需要重載delete
6. public:
7. A(){}
8. ~A(){}
9. };
實現strcpy()
char * strcpy(char* dest, const char* src)
{
assert(dest!=NULL&&src!=NULL);
char* res=dest;
while((*dest++=*src++)!=’\0’);
return res;
}
內聯函數與宏
用內聯取代宏:
1.內聯函數在運行時可調試,而宏定義不可以;
2.編譯器會對內聯函數的參數類型做安全檢查或自動類型轉換(同普通函數),而宏定義則不會;
3.內聯函數可以訪問類的成員變量,宏定義則不能;
4.在類中聲明同時定義的成員函數,自動轉化爲內聯函數。
抽象類、接口
抽象類是包含純虛函數的類
虛繼承
作用:爲了解決從不同途徑繼承來的同名的數據成員在內存中有不同的拷貝造成數據不一致問題,將共同基類設置爲虛基類。
這時從不同的路徑繼承過來的同名數據成員在內存中就只有一個拷貝,同一個函數名也只有一個映射。這樣不僅就解決了二義性問題,也節省了內存,避免了數據不一致的問題。
底層實現原理:底層實現原理與編譯器相關,一般通過虛基類指針實現,即各對象中只保存一份父類的對象,多繼承時通過
虛基類指針引用該公共對象,從而避免菱形繼承中的二義性問題。
多繼承的二義性
構造函數中可不可以拋出異常?析構函數呢?
理論上都可以拋出異常。
但析構函數最好不要拋出異常,將會導致析構不完全,從而有內存泄露
構造函數和析構函數中調用虛函數
可以,虛函數底層實現原理
C++中的空類,默認產生哪些類成員函數
class Empty
{
public:
Empty(); // 缺省構造函數
Empty( const Empty& ); // 拷貝構造函數
~Empty(); // 析構函數
Empty& operator=( const Empty& ); // 賦值運算符
Empty* operator&(); // 取址運算符
const Empty* operator&() const; // 取址運算符 const
};
函數指針
Int and(int a,int b){return a+b;}
int (*p)(int ,int );
p=and;
int c=(*p)(1,2);
Int型變量作爲bool使用
除了0是false其餘都爲true
Int a=-2;while(a++);//執行2次
int c=-2;
int a=c||0;//a=1
面向對象的特性
抽象、繼承、封裝、多態
重載運算符
. * :: sizeof ?: 不能重載
const A operator +(A &b)
{
return A(this.data+b.data)
}
指針和引用
★ 區別:
1.指針是一個實體,而引用僅是個別名;
2.引用使用時無需解引用(*),指針需要解引用;
3.引用只能在定義時被初始化一次,之後不可變;指針可變;
4.引用沒有const,指針有const;
5.引用不能爲空,指針可以爲空;
6.“sizeof引用”得到的是所指向的變量(對象)的大小,而“sizeof指針”得到的是指針本身(所指向的變量或對象的地址)的大小;
7.指針和引用的自增(++)運算意義不一樣;
8.從內存分配上看:程序爲指針變量分配內存區域,而引用不需要分配內存區域
基類析構函數爲虛函數
在實現多態時,當用基類操作派生類,在析構時防止只析構基類而不析構派生類的狀況發生,如果只調用基類的析構函數,則派生類沒有釋放掉,造成內存泄漏
C++11
auto i= 10;
auto s=”hello”;
強制類型轉換符
dynamic_cast
該轉換符用於將一個指向派生類的基類指針或引用轉換爲派生類的指針或引用
//B是D的基類,D是派生類
B* pb;
D* pd,md;
pb = &md;
pd=dynamic_cast<D *>(pb);
把指向派生類D的基類指針pb轉換爲派生類D的指針,然後將這個指針賦給派生類D的指針pd。
如果指向派生類的基類指針B想訪問派生類D中的除虛函數之外的成員時就需要把該指針轉換爲指向派生類D的指針,以達到訪問派生類D中特有的成員的目的,比如派生類D中含有特有的成員函數g(),這時可以這樣來訪問該成員dynamic_cast<D *>(pb)->g();因爲dynamic_cast轉換後的結果是一個指向派生類的指針,所以可以這樣訪問派生類中特有的成員。但是該語句不影響原來的指針的類型,即基類指針pb仍然是指向基類B的。如果單獨使用該指針仍然不能訪問派生類中特有的成員。
dynamic_cast的注意事項
dynamic_cast轉換符只能用於指針或者引用。dynamic_cast轉換符只能用於含有虛函數的類。dynamic_cast轉換操作符在執行類型轉換時首先將檢查能否成功轉換,如果能成功轉換則轉換之,如果轉換失敗,如果是指針則反回一個0值,如果是轉換的是引用,則拋出一個bad_cast異常,所以在使用dynamic_cast轉換之間應使用if語句對其轉換成功與否進行測試,比如pd = dynamic_cast(pb); if(pd){…}else{…},或者這樣測試if(dynamic_cast(pb)){…}else{…}。
因此,dynamic_cast操作符一次執行兩個操作。首先驗證被請求的轉換是否有效,只有轉換有效,操作符才實際進行轉換。基類的指針可以賦值爲指向派生類的對象,同樣,基類的引用也可以用派生類對象初始化,因此,dynamic_cast操作符執行的驗證必須在運行時進行。
const_cast操作符
該操作符用於改變const和volatile,const_cast最常用的用途就是刪除const屬性;
操作符不能改變類型的其他方面,他只能改變const或volatile,即const_cast不能把int改變爲double,但可以把const int改變爲int。const_cast只能用於指針或引用。
int a=3; const int *b=&a; int* c=const_cast(b); *c=4; cout<<a<<*c; 輸出爲兩個4
static_cast操作符
該操作符用於非多態類型的轉換,任何標準轉換都可以使用他,即static_cast可以把int轉換爲double,但不能把兩個不相關的類對象進行轉換,比如類A不能轉換爲一個不相關的類B類型。static_cast本質上是傳統c語言強制轉換的替代品。
reinterpret_cast操作符
該操作符用於將一種類型轉換爲另一種不同的類型,比如可以把一個整型轉換爲一個指針,或把一個指針轉換爲一個整型,因此使用該操作符的危險性較高,一般不應使用該操作符。
reinterpret_cast(重述轉換)主要是將數據從一種類型的轉換爲另一種類型。所謂“通常爲操作數的位模式提供較低層的重新解釋”也就是說將數據以二進制存在形式的重新解釋
String to int
#include <sstream> stringstream ss; int a=15454; ss<<a; string c=”str:”+ss.str(); cout<<c<<endl;
構造函數調用順序
先調用基類構造函數
在調用成員類構造函數
最後調用本身的構造函數
析構順序相反
宏
1定義一個宏,輸入兩個參數,並返回期中較小的一個。另外,當你寫下面的代碼時會發生什麼事?
least = MIN(*p++, b);
解答:
#define MIN(A,B) ((A) <= (B) ? (A) : (B))
MIN(*p++, b)會產生宏的副作用
剖析:
這個面試題主要考查面試者對宏定義的使用,宏定義可以實現類似於函數的功能,但是它終歸不是函數,而宏定義中括弧中的“參數”也不是真的參數,在宏展開的時候對“參數”進行的是一對一的替換。
程序員對宏定義的使用要非常小心,特別要注意兩個問題:
(1)謹慎地將宏定義中的“參數”和整個宏用用括弧括起來。所以,嚴格地講,下述解答:
1
2
#define MIN(A,B) (A) <= (B) ? (A) : (B)
#define MIN(A,B) (A <= B ? A : B )
都應判0分;
(2)防止宏的副作用。
宏定義#define MIN(A,B) ((A) <= (B) ? (A) : (B))對MIN(*p++, b)的作用結果是:
1
((*p++) <= (b) ? (*p++) : (b))
這個表達式會產生副作用,指針p會作2次++自增操作。
除此之外,另一個應該判0分的解答是:
1
#define MIN(A,B) ((A) <= (B) ? (A) : (B));
這個解答在宏定義的後面加“;”,顯示編寫者對宏的概念模糊不清,只能被無情地判0分並被面試官淘汰。
數組和指針
#include <iostream>
using namespace std;
void fun(int a[])
{
cout<<sizeof(a)<<endl;//4
*(a+1)=100;
cout<<*++a<<endl;//100
}
int main()
{
int a[10]={0,1,2,3,4,5,6,7,8,9};
cout<<sizeof(a)<<endl;
//cout<<*a<<*(++a)<<endl;//編譯出錯,a本身是常量指針
fun(a);
char *b="sfaasf";
char *c="123";
*b=”123”;//編譯出錯,b指向的內容是常量
b=c;
cout<<b<<endl;
return 0;
}
a數組的a是一個常量指針,不能修改,但a指向的內容可以修改,sizeof(a)等於數組大小*數組元素大小
當把a作爲形參傳入函數中時,a會退化爲一個指針,a本身和指向的內容都可以修改了,sizeof(a)就等於指針的大小
指針b所指向的內容是常量,不能修改,但b本身是個指針,可以修改;
多線程加鎖
多線程調用時要進行保護時,主要是針對全局變量和靜態變量的,函數內的局部變量不會受到影響
拷貝構造函數
三種調用拷貝構造函數的情況:
1. 需要用一個對象去初始化同一個類的另一個新對象;
2. 函數調用時,形參和實參的結合
3. 函數返回值爲對象時
淺拷貝和深拷貝
淺拷貝是指在對象賦值時,只對對象中的數據成員進行簡單的賦值,但對於存在動態成員(指針等),就會出現問題,使得兩個對象的動態成員指向了同一個地址,而不是不同地址,內容相同。
自己寫拷貝構造函數
class A{ public: A(){p =new int();} A(0){p=new int(0);} A(const A& a) { p= new int(); *p=*(a.p) } private: Int *p; }
初始化列表
(1)
class classA {...}; class classB { public: classB(classA a) {mA = a;} private: classA mA; }; (2) class classA {...}; class classB { public: classB(classA a): mA(a) {} private: classA mA; };
第二種比第一種效率更高
第一種方法,先調用a的默認構造函數,再調用operator=賦值函數;
第二種方法,直接調用拷貝構造函數,用初始化列表來進行構造,初始化列表的順序只和變量申明的順序有關
宏
宏只是預定義的函數,在編譯階段不進行類型安全性檢查,在編譯的時候將對應函數用宏命令替換。對程序性能無影響
printf()
printf()的返回值是輸出內容的大小
Const
const int *a;//const在*a的前面,指的是*a爲常量,不能變,即a指向的內容不能變
int const *a;//同上
int *const a;//const在a的前面,a爲常量,即a這個指針爲常量,a不能變
const int fun(const int a) const;
第一個const修飾返回值 第二個修飾參數第三個修飾調用對象
int getValue() const;//常成員函數
int getValue();
const關鍵字可以用於對重載函數的區分
常成員函數不能更新類的成員變量,也不能調用該類中沒有用const修飾的成員函數,只能調用常成員函數
調用常成員函數的對象必須是常量對象如A const a(1); a.getValue();//此時調用的就是常成員函數
四種用法:
1. 修飾常量
2. 修飾函數參數 參數作爲指針、引用傳遞時纔有意義,值傳遞無意義
3. 修飾函數返回值
4. 修飾函數的定義體:任何不會修改數據成員的函數都應該聲明爲const類型。如果在編寫const成員函數時,不慎修改了數據成員,或者調用了其它非const成員函數,編譯器將指出錯誤,這無疑會提高程序的健壯性
接口
C++中的接口是指只包含純虛函數的抽象類,不能被實例化。
一個類可以實現多個接口(多重繼承)
(開)幾次方
#include <math.h>
X的y次方pow(x,y);
X開y次方pow(x,1.0/y);
輸入中有空格
當輸入中有空格的時候,使用cin scanf()遇到空格會停止輸入
要採用gets()或者cin.getline(a,10000);第一個是要接受輸入的指針,第二個是輸入緩衝區大小
內存管理
簡單理解:
1. 靜態存儲區:內存在程序編譯的時候就已經分配好,這塊內存在程序的整個運行期間都存在。它主要存放靜態數據、全局數據和常量。
2. 棧區:在執行函數時,函數(包括main函數)內局部變量的存儲單元都可以在棧上創建,函數執行結束時這些存儲單元自動被釋放。棧內存分配運算內置於處理器的指令集中,效率很高,但是分配的內存容量有限。(任何變量都處於站區,例如int a[] = {1, 2},變量a處於棧區。數組的內容也存在於棧區。)
3. 堆區:亦稱動態內存分配。程序在運行的時候用malloc或new申請任意大小的內存,程序員自己負責在適當的時候用free或delete釋放內存。動態內存的生存期可以由我們決定,如果我們不釋放內存,程序將在最後才釋放掉動態內存。但是,良好的編程習慣是:如果某動態內存不再使用,需要將其釋放掉,並立即將指針置位NULL,防止產生野指針。
棧:在執行函數時,函數內局部變量的存儲單元都可以在棧上創建,函數執行結束時這些存儲單元自動被釋放。棧內存分配運算內置於處理器的指令集中,效率很高,但是分配的內存容量有限。
堆:就是那些由new分配的內存塊,他們的釋放編譯器不去管,由我們的應用程序去控制,一般一個new就要對應一個delete。如果程序員沒有釋放掉,那麼在程序結束後,操作系統會自動回收。
自由存儲區:就是那些由malloc等分配的內存塊,他和堆是十分相似的,不過它是用free來結束自己的生命的。
全局/靜態存儲區:全局變量和靜態變量被分配到同一塊內存中,在以前的C語言中,全局變量又分爲初始化的和未初始化的,在C++裏面沒有這個區分了,他們共同佔用同一塊內存區。
常量區:這是一塊比較特殊的存儲區,他們裏面存放的是常量,不允許修改
setw(8) swefill(*)
setw(int n)用來控制輸出間隔
例如:
cout<<'s'<<setw(8)<<'a'<<endl;
則在屏幕顯示
s a
//s與a之間有7個空格,setw()只對其後面緊跟的輸出產生作用,如上例中,表示'a'共佔8個位置,不足的用空格填充。若輸入的內容超過setw()設置的長度,則按實際長度輸出。
setw()默認填充的內容爲空格,可以setfill()配合使用設置其他字符填充。
如
cout<<setfill('*')<<setw(5)<<'a'<<endl;
則輸出:
****a //4個*和字符a共佔5個位置
浮點數表示
Float 8位階碼+符號位.+23位尾數
單精度浮點數有效位是7 10^7<2^(23+1)<10^8考慮第7位四捨五入,最少6位
階碼用移碼錶示:2^7+實際階數的2進製表示
Double 11位階碼+符號位+52位尾數
雙精度浮點數有效位是16 10^16<2^(52+1)<10^17,考慮第16位四捨五入,最少15位
類的大小
Class A
{
Static int c;
Int c;
Void a();
Virtul void b();
};
類A的大小是 所有非靜態成員變量大小之和+虛函數指針大小
文件操作
ofstream:寫操作(輸出)的文件類(由ostream引申而來)
ifstream:讀操作(輸入)的文件類(由istream引申而來)
fstream:可同時讀寫操作的文件類(由iostream引申而來)
#include <fiostream.h>
int main () {
ofstream examplefile ("example.txt");
if (examplefile.is_open()) {
examplefile << "This is a line.\n";
examplefile << "This is another line.\n";
examplefile.close();
}
return 0;
}
#include <iostream.h>
#include <fstream.h>
#include <stdlib.h>
int main () {
char buffer[256];
ifstream examplefile ("example.txt");
if (! examplefile.is_open())
{ cout << "Error opening file"; exit (1); }
while (! examplefile.eof() ) {
examplefile.getline (buffer,100);
cout << buffer << endl;
}
return 0;
}
類不聲明成員變量訪問修飾符默認private
結構體默認是public
友元函數 友元類
友元函數在類外部定義,在類中聲明的可以訪問類的隱藏成員的函數,不屬於這個類
A爲B的友元類,則A的所有成員函數爲B的友元函數
class INTEGER { friend void Print(const INTEGER& obj);//聲明友元函數 }; void Print(const INTEGER& obj){ //函數體} void main() { INTEGER obj; Print(obj);//直接調用}
模板
Template<class(typename) T>
//定義完一個模板T後立即使用,使用後
class Temp
{
public: Temp(T t){this.a=t}
private:T a;
}
模板的實例化
Temp<int> t1(3); Temp<string> t2(“abcd”); Template <class T1,class T2> //int i=0; 錯誤,模板定義和使用之間不得插入任何無關內容 T1 func( T1 a,T2 b) { cout<<b<<endl; return a; } int c=func(1,”abc”);
類的繼承
Private屬性不能繼承
Private繼承,父類public,protected屬性在子類中變爲private
Protected繼承,父類public,protected屬性變爲protected
Public集成,父類public protected不改變
Private protected public訪問範圍
Private:該類中的成員函數,該類的友元函數,其餘都不能訪問
Protected:該類的成員函數,子類的成員函數,友元函數,不能被類的對象訪問
Public:類中的成員函數,子類的函數,友元函數,類的對象都可以訪問
注意:友元函數報告三種:一種普通的友元函數(非成員函數),設爲友元的其他類的成員函數,設爲友元類中的所有成員函數
C++多態性的實現
多態:指當不同的對象收到相同的消息時,產生不同的動作
編譯時多態:函數重載、運算符重載——靜態綁定
運行時多態:虛函數機制——動態綁定
C++的多態性用一句話概括就是:在基類的函數前加上virtual關鍵字,在派生類中重寫該函數,運行時將會根據對象的實際類型來調用相應的函數。如果對象類型是派生類,就調用派生類的函數;如果對象類型是基類,就調用基類的函數。
重寫、重載、重定義
.重寫(override):
父類與子類之間的多態性。子類重新定義父類中有相同名稱和參數的虛函數。
1)被重寫的函數不能是static的。必須是virtual的(即函數在最原始的基類中被聲明爲virtual )。
2)重寫函數必須有相同的類型,名稱和參數列表(即相同的函數原型)
3)重寫函數的訪問修飾符可以不同。儘管virtual是private的,派生類中重寫改寫爲public,protected也是可以的
2.重載(overload):
指函數名相同,但是它的參數表列個數或順序,類型不同。但是不能靠返回類型來判斷。
3.重定義(redefining):
子類重新定義父類中有相同名稱的非虛函數(參數列表可以不同)。
重寫與重載的區別 (override) PK (overload)
1、方法的重寫是子類和父類之間的關係,是垂直關係;方法的重載是同一個類中方法之間的關 系,是水平關係。
2、重寫要求參數列表相同;重載要求參數列表不同。
3、重寫關係中,調用那個方法體,是根據對象的類型(對象對應存儲空間類型)來決定;重載關係,是根據調用時的實參表與形參表來選擇方法體的。
Volatile
修飾符,修飾後的變量可以防止被編譯優化,每次取值時會逐語句取值(多線程)
STL類庫編程
各種容器的優缺點及底層實現
sString
#include <string>
string a,b;
cin>>a>>b;
a.insert(a.length(),b);
a[0]
VECTOR向量
#include <vector> Vector<int> a,b; a.push_back(3); a.erase()
QUEUE隊列
#include <queue> queue<int> a; a.push(1); a.push(2); cout<<a.front()<<a.back(); a.pop(); a.size(); a.empty();
priority_queue 優先隊列
STACK棧
#include <stack> stack<int> a; a.push(1); cout<<a.top();- a.pop(1); a.empty(); a.size();
LIST雙向鏈表
將元素按順序儲存在鏈表中.與 向量(vectors)相比,它允許快速的插入和刪除,但是隨機訪問卻比較慢
#include <list>
list<int> l,l2;
l.assign(8,1);//將l變爲8個1(清空掉原來的內容,重新賦值8個1)
l2.assign(l.begin(),l.end());把l的內容賦給l2
l.front();訪問第一個元素
l.back();訪問最後一個元素
l.push_back(5);在鏈表尾部插入新元素
l.push_front(6);在鏈表首部插入新元素
l.pop_back();
i.pop_front();
l.begin()返回頭元素的迭代器
l.end();返回尾元素的迭代器
l.rbegin();返回頭元素的反向迭代器(雙向鏈表)
l.rend();返回尾元素的反向迭代器
l.insert(l.begin()+5,1);制定位置插入
l.erase(l.begin()+4)刪除指定位置
l.merge(l2) l與l2歸併(合併後排序)升序
l.splice(l.end(),l2);把l2插入到l後面
swap(l,l2);交換兩個鏈表
l.sort(comp);用自定義比較函數comp給l排序
bool comp(const int &a,const int &b) { return a>b; } l.reverse(); 反轉 l.unique(); 刪除重複元素 l.size(); l.clear(); l.empty(); unique
MAP映射
Map<key,value>
#include <map>
#include <algorithm>
map<string,int> m;
//插入新元素
m.insert(pair<string,int>(“aaa”,1)));
m.insert(map<string,int>::value_type(“bbb”,2));
m[“ccc”]=3;
map<string,int>::iterator it;
it=m.find(“aaa”);
if(it==m.end())
cout<<”can’t find”;
m.erase(it);刪除key爲aaa的元素
MAP自動按照key值升序排列,不能對它進行排序
m.upper_bound(“bbb”);//返回鍵值大於”bbb”的第一個元素的位置
swap();
底層實現紅黑樹
SET集合
實現了紅黑樹的平衡二叉檢索樹的數據結構
入元素時,它會自動調整二叉樹的排列,把元素放到適當的位置,以保證每個子樹根節點鍵值大於左子樹所有節點的鍵值,小於右子樹所有節點的鍵值;另外,還得保證根節點左子樹的高度與右子樹高度相等。
平衡二叉檢索樹使用中序遍歷算法,檢索效率高於vector、deque和list等容器,另外使用中序遍歷可將鍵值按照從小到大遍歷出來。
構造set集合主要目的是爲了快速檢索,不可直接去修改鍵值。
多線程開發
哈希衝突及解決
哈希存儲:犧牲空間存儲換來時間上的效率
如有1000個數據,但我們開10000的空間來存儲,對數據的key值得到hash值存到對應的位置,這樣在查找時,就能根據key值很快的找到某個數據。
但是有時候對於數據的key值範圍不能確定,所以多個不同的key得到的hash值可能相同,這時就產生了哈希衝突,
對於常見的取餘數得到hash值的方法,可以採用開放地址法來處理hash衝突,hash=(hash(key)+d)%n給衝突的hash值繼續加一個增量d,繼續取餘數,直到找到一個不衝突的位置爲止
補碼的好處
原碼在做加減運算時十分複雜,如8-2就等於8+(-2),而-2在計算機中就是用2的補碼錶示的,直接用8的原碼加上2的補碼
數據結構
基本數據類型
32位操作系統
int 4字節
long 4字節
char 1字節
*指針 4字節
float 4字節
Short int 2字節
long long int 8字節
double 8字節
64位操作系統
long 8字節
* 8字節
結構體:所有成員加起來,有內存對齊;
聯合:成員最大長度,有內存對齊
枚舉:佔一個int大小,枚舉成員的默認值等
數組
char *a和char a[]的區別
char*a,a這個指針作爲變量是存儲在棧上的,可以修改,但指針指向的內容是存放在常量區,無法修改
char *a=”fasfsa”;
char *b=”gasgsag”;
a=b;//正確
*a=*b;//錯誤
char a[] a這個指針變量和數組中的內容都是存儲在棧上的,可以修改
結構體
typedef struct myStruct { int val; string a; myStruct(int val,string a) { this->val=val; this->a=a; } }MyStruct; MyStruct b(5,”sfafasf”); 結構體對齊,sizeof()佔多少字節 struct a{ struct b{ struct c{ int a; int a; long long int a; int b; char b; int b; char c; int c; long long int c; short int d; short int d; char c; };共佔12字節 }; 16字節 } 4*8=32字節
鏈表
反轉、找到中間元素等
樹
二叉樹
霍夫曼樹
帶權路徑最小的最優樹
二叉、紅黑等樹,遍歷
圖
隊列
堆
棧
算法
排序
排序有內部排序和外部排序,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大,一次不能容納全部的排序記錄,在排序過程中需要訪問外存。
插入排序
直接插入
默認最前面的是已經排好序的,然後依次把後面的插入到合適的位置。
適用於數據量較小
時間複雜度:O(n^2)
最佳複雜度:O(n)排好序的時候
最差:O(n^2)倒序
void insertSort(int a[])
{
If(a.size()<2)
return;
for(int i = 1;i<a.size();i++)
{
int key =a[i];
int j = i-1;
while(j>0&&a[j]>key)
{
a[j+1]=a[j];
j--;
}
a[j+1]=key;
}
}
希爾排序
時間複雜度:O(n^1.3)
先將要排序的一組記錄按某個增量d(n/2,n爲要排序數的個數)分成若干組子序列,每組中記錄的下標相差d.對每組中全部元素進行直接插入排序,然後再用一個較小的增量(d/2)對它進行分組,在每組中再進行直接插入排序。繼續不斷縮小增量直至爲1,最後使用直接插入排序完成排序。
void ShellInsertSort(int a[], int n, int dk)
{
if(n<2)
return;
for(int i=dk; i<n ; i++)
{
if(a[i-dk] > a[i])
{
int key = a[i];
int j = i - dk;
while(j>=0 && a[j] > key)
{
a[j+dk] = a[j];
j -= dk;
}
a[j+dk] = key;
}
}
}
void ShellSort(int a[], int n)
{
int dk = n/2;
while(dk>=1)
{
ShellInsertSort(a, n, dk);
dk /=2;
}
}
選擇排序
簡單選擇排序
每次選擇一個最大值放到最後(最小值放到最前面)
時間複雜度:O(n^2)
void selectSort(int a[]) { If(a.size()<2) return; for(int i=0;i<a.size()-1;i++) { int min= i; for(int j=i+1;j<a.size();j++) { If(a[j]<a[min]) min=j; } swap(a[i],a[min]); } }
改進每趟選出當前最大和最小,最多只需要N/2趟排序
堆排序
時間複雜度:O(nlogn)
建立堆的複雜度是O(n),只建立一次
調整堆的時間複雜度是o(logn),調用n-1次 所以是nlogn
不穩定
//array是待調整的堆數組,i是待調整的數組元素的位置,nlength是數組的長度
//本函數功能是:根據數組array構建大根堆
void HeapAdjust(int array[],int i,int nLength)
{
int nChild;
int nTemp;
for(;2*i+1<nLength;i=nChild)
{
//子結點的位置=2*(父結點位置)+1
nChild=2*i+1;
//得到子結點中較大的結點
if(nChild<nLength-1&&array[nChild+1]>array[nChild])++nChild;
//如果較大的子結點大於父結點那麼把較大的子結點往上移動,替換它的父結點
if(array[i]<array[nChild])
{
nTemp=array[i];
array[i]=array[nChild];
array[nChild]=nTemp;
}
else break; //否則退出循環
}
}
//堆排序算法
void HeapSort(int array[],int length)
{
int i;
//調整序列的前半部分元素,調整完之後第一個元素是序列的最大的元素
//length/2-1是最後一個非葉節點,此處"/"爲整除
for(i=length/2-1;i>=0;--i)
HeapAdjust(array,i,length);
//從最後一個元素開始對序列進行調整,不斷的縮小調整的範圍直到第一個元素
for(i=length-1;i>0;--i)
{
//把第一個元素和當前的最後一個元素交換,
//保證當前的最後一個位置的元素都是在現在的這個序列之中最大的
交換arry[i] arry[0]
//不斷縮小調整heap的範圍,每一次調整完畢保證第一個元素是當前序列的最大值
HeapAdjust(array,0,i);
}
}
交換排序
冒泡
通過兩兩交換,每次找出剩下元素中的最大值(最小值)
時間複雜度:O(n^2)
Void bubbleSort(int a[]) { If(a.size()<2) return; for(int i=0;i<a.size();i++) { for(int j=0;j<a.size()-i-1;j++) { If(a[j]>a[j+1]) Swap(a[j],a[j+1]); } } }
改進冒泡1
.設置一標誌性變量pos,用於記錄每趟排序中最後一次進行交換的位置。由於pos位置之後的記錄均已交換到位,故在進行下一趟排序時只要掃描到pos位置即可
1. void Bubble_1 ( int r[], int n) {
2. int i= n -1; //初始時,最後位置保持不變
3. while ( i> 0) {
4. int pos= 0; //每趟開始時,無記錄交換
5. for (int j= 0; j< i; j++)
6. if (r[j]> r[j+1]) {
7. pos= j; //記錄交換的位置
8. int tmp = r[j]; r[j]=r[j+1];r[j+1]=tmp;
9. }
10. i= pos; //爲下一趟排序作準備
11. }
12. }
改進冒泡2
統冒泡排序中每一趟排序操作只能找到一個最大值或最小值,我們考慮利用在每趟排序中進行正向和反向兩遍冒泡的方法一次可以得到兩個最終值(最大者和最小者) , 從而使排序趟數幾乎減少了一半。
1. void Bubble_2 ( int r[], int n){
2. int low = 0;
3. int high= n -1; //設置變量的初始值
4. int tmp,j;
5. while (low < high) {
6. for (j= low; j< high; ++j) //正向冒泡,找到最大者
7. if (r[j]> r[j+1]) {
8. tmp = r[j]; r[j]=r[j+1];r[j+1]=tmp;
9. }
10. --high; //修改high值, 前移一位
11. for ( j=high; j>low; --j) //反向冒泡,找到最小者
12. if (r[j]<r[j-1]) {
13. tmp = r[j]; r[j]=r[j-1];r[j-1]=tmp;
14. }
15. ++low; //修改low值,後移一位
16. }
17. }
快速排序
時間複雜度:O(nlogn)
空間複雜度:O(nlogn)每一趟快排過後需要花費o(n)來存儲當前狀態,再遞歸進入下一趟快排,共需logn趟,所以空間複雜度nlogn
不穩定
1. void swap(int *a, int *b)
2. {
3. int tmp = *a;
4. *a = *b;
5. *b = tmp;
6. }
7.
8. int partition(int a[], int low, int high)
9. {
10. int privotKey = a[low]; //基準元素
11. while(low < high){ //從表的兩端交替地向中間掃描
12. while(low < high && a[high] >= privotKey) --high; //從high 所指位置向前搜索,至多到low+1 位置。將比基準元素小的交換到低端
13. swap(&a[low], &a[high]);
14. while(low < high && a[low] <= privotKey ) ++low;
15. swap(&a[low], &a[high]);
16. }
17. print(a,10);
18. return low;
19. }
20.
21.
22. void quickSort(int a[], int low, int high){
23. if(low < high){
24. int privotLoc = partition(a, low, high); //將表一分爲二
25. quickSort(a, low, privotLoc -1); //遞歸對低子表遞歸排序
26. quickSort(a, privotLoc + 1, high); //遞歸對高子表遞歸排序
27. }
28. }
快速排序的改進
對於長度大於8的用快排,小於8的用插入排序
歸併排序
時間複雜度:O(nlogn)
穩定!
將兩個順序序列合併成一個順序序列的方法
第一步:申請空間,使其大小爲兩個已經排序序列之和,該空間用來存放合併後的序列
第二步:設定兩個指針,最初位置分別爲兩個已經排序序列的起始位置
第三步:比較兩個指針所指向的元素,選擇相對小的元素放入到合併空間,並移動指針到下一位置
重複步驟3直到某一指針超出序列尾
將另一序列剩下的所有元素直接複製到合併序列尾
1. void Merge(ElemType *r,ElemType *rf, int i, int m, int n)
2. {
3. int j,k;
4. for(j=m+1,k=i; i<=m && j <=n ; ++k){
5. if(r[j] < r[i]) rf[k] = r[j++];
6. else rf[k] = r[i++];
7. }
8. while(i <= m) rf[k++] = r[i++];
9. while(j <= n) rf[k++] = r[j++];
10. }
桶排序/基數排序
時間複雜度:線性接近O(n)
關鍵碼排序,撲克牌按花色、大小排序
分治
分治法是一個遞歸地求解問題的過程
分治法在每一層遞歸上有三個步驟:
1. 分解:通過某種方法,將原問題分解成若干個規模較小,相互獨立,與原問題形式相同的子問題
2. 解決:若子問題規模小到可以直接解決(稱爲基本問題),則直接解決,否則把子問題進一步分解,遞歸求解
3. 合併:通過某種方法,將子問題的解合併爲原問題的解
二分法
給一個二叉樹和一個值,判斷是否存在一條由根到葉子的路徑使得路徑節點之和等於給定值
遞歸
搜索BFS DFS
BFS入隊
DFS遞歸,入棧
模擬
貪心
動規
回溯
設計模式
OO設計的原則
策略模式
生產者消費者
1. 解耦,當生產者的生產函數改變時,消費者不會受到影響
2. 支持併發,解決生成消費速度不匹配等問題,不會阻塞
觀察者模式
簡單工廠、工廠方法、抽象工廠
MVC
單例
singleton單例模式,簡單說就是隻有自己,爲自己設計。降低了重複使用。降低資源使用率
flyweight設計中的享元模式,避免大量擁有相同內容的小類的開銷,因爲他讓大家共享一個類
LINUX
錯題點
權限命令
# 改變權限
chmod 777 filepath 指定文件filepath爲所有用戶可讀,可寫,可執行
讀寫執行分別對應數字1 2 4,加起來就是7
# 改變所有者
chown test filepath
改變filepath 的所有者爲test
# 改變所屬組
chgrp user filepath
改變filepath 的所屬組爲user
常用命令
ls cd pwd cp chmod chwon等