頂層const和底層const 變數自身不能改變的是頂層const,比如const int,int *const的常量指針,變數所指的對象或者所引用的對象是不能改變的,而變數自身是可以改變的是底層const,比如const int *的指向常量對象的非常量指針。 左值和右值 左值是有具體存儲地址的值 ...
頂層const和底層const
變數自身不能改變的是頂層const,比如const int,int *const的常量指針,變數所指的對象或者所引用的對象是不能改變的,而變數自身是可以改變的是底層const,比如const int *的指向常量對象的非常量指針。
左值和右值
左值是有具體存儲地址的值,表現為=左邊的值,右值是沒有具體存儲地址,比如寄存器中的值,表現為=右邊的值。名字的左值:該名字代表的存儲單元的地址;名字的右值:該名字代表的存貯單元的內容。
智能指針
// 初始化方式1
std::unique_ptr<int> up1(new int(1));
std::unique_ptr<int[]> up2(new int[3]);
// 初始化方式2
std::unique_ptr<int> up3;
up3.reset(new int(1));
std::unique_ptr<int[]> up4;
up4.reset(new int[3]);
// 初始化方式3,推薦
std::unique_ptr<int> up5 = std::make_unique<int>(1);
std::unique_ptr<int[]> up6(std::make_unique<int[]>(3));
/* 沒有嘗試過std::unique_ptr<int> up(std::make_unique<int>(1));
* 和std::unique_ptr<int[]> up = std::make_unique<int[]>(3);是否正確
這樣獲得的up內就包含了指向創建的記憶體的指針,可以用up.get()來獲取該指針,和直接使用up是等價的。
shared_ptr和weak_ptr見cubox收藏,auto_ptr在C++11已經棄用。
模板
函數模板:
// 定義
template <typename T>
inline T const& Max (T const& a, T const& b) {
return a < b ? b : a;
}
// 使用
int i = 1, j = 2;
cout << Max(i, j);
類模板:
// 定義
template <class T>
class Stack {
private:
vector<T> elems;
public:
void push(T const&);
void pop();
T top() const;
bool empty() const{
return elems.empty();
}
};
template <class T>
void Stack<T>::push (T const& elem) {
elems.push_back(elem);
}
template <class T>
void Stack<T>::pop () {
if (elems.empty()) {
throw out_of_range("Stack<>::pop(): empty stack");
}
elems.pop_back();
}
template <class T>
T Stack<T>::top () const
{
if (elems.empty()) {
throw out_of_range("Stack<>::top(): empty stack");
}
return elems.back();
}
// 使用
Stack<int> intStack;
Stack<string> stringStack;
generate函數
std::generate(v.begin(), v.end(), std::rand); //使用隨機數填充vector向量v
count_if函數
int num = count_if(v.begin(), v.end(), f); //f是自定義的函數,返回類型為布爾類型,count_if函數統計vector向量v中符合f條件的元素個數
lambda表達式
[capture] (params) opt -> ret {};
其中carpture是捕獲列表,params是參數,opt是選項,ret則是返回值的類型,body則是函數的具體實現。
-
捕獲列表描述了lambda表達式可以訪問上下文中的哪些變數:
[]:表示不捕獲任何變數。
[=]:表示按值捕獲變數,也就是說在lambda函數內使用lambda之外的變數時,使用的是拷貝。
[&]:表示按引用捕獲變數,也就是說在lambda函數內使用lambda之外的變數時,使用的是引用。
[this]:值傳遞捕獲當前的this。 -
params表示lambda的參數,用在{}中。
-
opt表示lambda的選項,例如mutable。
-
ret表示lambda的返回類型,也可以顯示指明返回類型,lambda會自動推斷返回類型,但是值得註意的是只有當lambda的表達式僅有一條return語句時,自動推斷才是有效的。
靜態變數
全局(靜態)存儲區:分為 DATA 段和 BSS 段。DATA 段(全局初始化區)存放初始化的全局變數和靜態變數;BSS 段(全局未初始化區)存放未初始化的全局變數和靜態變數。程式運行結束時自動釋放。其中BBS段在程式執行之前會被系統自動清0,所以未初始化的全局變數和靜態變數在程式執行之前已經為0。存儲在靜態數據區的變數會在程式剛開始運行時就完成初始化,也是唯一的一次初始化。
一般程式把新產生的動態數據存放在堆區,函數內部的自動變數存放在棧區。自動變數一般會隨著函數的退出而釋放空間,靜態數據(即使是函數內部的靜態局部變數)也存放在全局數據區。全局數據區的數據並不會因為函數的退出而釋放空間。
對於C語言的全局和靜態變數,初始化發生在任何代碼執行之前,屬於編譯期初始化。
而C++標準規定:****全局或靜態對象當且僅當對象首次用到時才進行構造。
回調
自己的函數調用了別人的函數,其中別人的函數又調用了自己的函數,就是回調;回調是函數指針的應用場景。
比如自己調用sort函數,使用自己定義的cmp比較函數,這就是回調,因為sort調用了自己的cmp比較函數,並且是通過函數指針的形式調用的(sort在實現時尋找了cmp函數的入口地址)。
nullptr調用成員函數可以嗎?為什麼?
能,因為在編譯時對象就綁定了函數地址,和指針空不空沒關係。
說說什麼是野指針,怎麼產生的,如何避免?
-
概念:野指針就是指針指向的位置是不可知的(隨機的、不正確的、沒有明確限制的)
-
產生原因:釋放記憶體後指針不及時置空(野指針),依然指向了該記憶體,那麼可能出現非法訪問的錯誤。這些我們都要註意避免。
-
避免辦法:
(1)初始化置NULL
(2)申請記憶體後判空
(3)指針釋放後置NULL
(4)使用智能指針
說說內聯函數和巨集函數的區別?
-
巨集定義不是函數,但是使用起來像函數。預處理器用複製巨集代碼的方式代替函數的調用,省去了函數壓棧退棧過程,提高了效率;而內聯函數本質上是一個函數,內聯函數一般用於函數體的代碼比較簡單的函數,不能包含複雜的控制語句,while、switch,並且內聯函數本身不能直接調用自身。
-
巨集函數是在預編譯的時候把所有的巨集名用巨集體來替換,簡單的說就是字元串替換 ;而內聯函數則是在編譯的時候進行代碼插入,編譯器會在每處調用內聯函數的地方直接把內聯函數的內容展開,這樣可以省去函數的調用的開銷,提高效率。
-
巨集定義是沒有類型檢查的,無論對還是錯都是直接替換;而內聯函數在編譯的時候會進行類型的檢查,內聯函數滿足函數的性質,比如有返回值、參數列表等。
★說說new和malloc的區別,各自底層實現原理
-
new是操作符,而malloc是函數。
-
new在調用的時候先分配記憶體,在調用構造函數,釋放的時候調用析構函數;而malloc沒有構造函數和析構函數。
-
malloc需要給定申請記憶體的大小,返回的指針需要強轉;new會調用構造函數,不用指定記憶體的大小,返回指針不用強轉。
-
new可以被重載;malloc不行。
-
new分配記憶體更直接和安全。
-
new發生錯誤拋出異常,malloc返回null。
答案解析
malloc底層實現:當開闢的空間小於 128K 時,調用 brk()函數;當開闢的空間大於 128K 時,調用mmap()。malloc採用的是記憶體池的管理方式,以減少記憶體碎片。先申請大塊記憶體作為堆區,然後將堆區分為多個記憶體塊。當用戶申請記憶體時,直接從堆區分配一塊合適的空閑快。採用隱式鏈表將所有空閑塊,每一個空閑塊記錄了一個未分配的、連續的記憶體地址。
new底層實現:關鍵字new在調用構造函數的時候實際上進行瞭如下的幾個步驟:
-
創建一個新的對象
-
將構造函數的作用域賦值給這個新的對象(因此this指向了這個新的對象)
-
執行構造函數中的代碼(為這個新對象添加屬性)
-
返回新對象
★程式啟動的過程
-
操作系統首先創建相應的進程並分配私有的進程空間,然後操作系統的載入器負責把可執行文件的數據段和代碼段映射到進程的虛擬記憶體空間中。
-
載入器讀入可執行程式的導入符號表,根據這些符號表可以查找出該可執行程式的所有依賴的動態鏈接庫。
-
載入器針對該程式的每一個動態鏈接庫調用LoadLibrary 。
(1)查找對應的動態庫文件,載入器為該動態鏈接庫確定一個合適的基地址。
(2)載入器讀取該動態鏈接庫的導入符號表和導出符號表,比較應用程式要求的導入符號是否匹配該庫的導出符號。
(3)針對該庫的導入符號表,查找對應的依賴的動態鏈接庫,如有跳轉,則跳到3
(4)調用該動態鏈接庫的初始化函數。
-
初始化應用程式的全局變數,對於全局對象自動調用構造函數。
請簡述一下atomoic記憶體順序(網上搜不到)
有六個記憶體順序選項可應用於對原子類型的操作:
-
memory_order_relaxed:在原子類型上的操作以自由序列執行,沒有任何同步關係,僅對此操作要求原子性。
-
memory_order_consume:memory_order_consume只會對其標識的對象保證該對象存儲先行於那些需要載入該對象的操作。
-
memory_order_acquire:使用memory_order_acquire的原子操作,當前線程的讀寫操作都不能重排到此操作之前。
-
memory_order_release:使用memory_order_release的原子操作,當前線程的讀寫操作都不能重排到此操作之後。
-
memory_order_acq_rel:memory_order_acq_rel在此記憶體順序的讀-改-寫操作既是獲得載入又是釋放操作。沒有操作能夠從此操作之後被重排到此操作之前,也沒有操作能夠從此操作之前被重排到此操作之後。
-
memory_order_seq_cst:memory_order_seq_cst比std::memory_order_acq_rel更為嚴格。memory_order_seq_cst不僅是一個"獲取釋放"記憶體順序,它還會對所有擁有此標簽的記憶體操作建立一個單獨全序。
除非你為特定的操作指定一個順序選項,否則記憶體順序選項對於所有原子類型預設都是memory_order_seq_cst。
構造函數分類
預設構造函數、初始化構造函數、拷貝構造函數、移動構造函數
//預設構造函數
Student()
{
num=1001;
age=18;
}
//初始化構造函數
Student(int n,int a):num(n),age(a){}
//拷貝構造函數
Test(const Test& t)
{
this->i = t.i;
this->p = new int(*t.p);
}
//移動構造函數:用於將其他類型的變數,隱式轉換為本類對象
Student(int r)
{
int num=1004;
int age= r;
}
說說一個類,預設會生成哪些函數
-
無參的構造函數
-
拷貝構造函數
-
賦值運算符
Empty& operator = (const Empty& copy) { }
-
析構函數(非虛)
★說說 C++ 類對象的初始化順序,有多重繼承情況下的順序
參考答案
-
創建派生類的對象,基類的構造函數優先被調用(也優先於派生類里的成員類);
-
如果類裡面有成員類,成員類的構造函數優先被調用(也優先於該類本身的構造函數);
-
基類構造函數如果有多個基類,則構造函數的調用順序是某類在類派生表中出現的順序而不是它們在成員初始化表中的順序;
-
成員類對象構造函數如果有多個成員類對象,則構造函數的調用順序是對象在類中被聲明的順序而不是它們出現在成員初始化表中的順序;
-
派生類構造函數,作為一般規則派生類構造函數應該不能直接向一個基類數據成員賦值而是把值傳遞給適當的基類構造函數,否則兩個類的實現變成緊耦合的(tightly coupled)將更加難於正確地修改或擴展基類的實現(基類設計者的責任是提供一組適當的基類構造函數)。
-
綜上可以得出,初始化順序:
父類構造函數–>成員類對象構造函數–>自身構造函數
其中成員變數的初始化與聲明順序有關,構造函數的調用順序是類派生列表中的順序。
析構順序和構造順序相反。
簡述下向上轉型和向下轉型
-
子類轉換為父類:向上轉型,使用dynamic_cast(expression),這種轉換相對來說比較安全不會有數據的丟失;
-
父類轉換為子類:向下轉型,可以使用強制轉換,這種轉換時不安全的,會導致數據的丟失,原因是父類的指針或者引用的記憶體中可能不包含子類的成員的記憶體。
★模板的實例化和具體化
// #1 模板定義
template<class T>
struct TemplateStruct
{
TemplateStruct()
{
cout << sizeof(T) << endl;
}
};
// #2 模板顯示實例化
template struct TemplateStruct<int>;
// #3 模板具體化
template<> struct TemplateStruct<double>
{
TemplateStruct() {
cout << "--8--" << endl;
}
};
int main()
{
TemplateStruct<int> intStruct;
TemplateStruct<double> doubleStruct;
// #4 模板隱式實例化
TemplateStruct<char> llStruct;
}
運行結果:
4
--8--
1
★簡述一下移動構造函數,什麼庫用到了這個函數?
C++11中新增了移動構造函數。與拷貝類似,移動也使用一個對象的值設置另一個對象的值。但是,又與拷貝不同的是,移動實現的是對象值真實的轉移(源對象到目的對象):源對象將丟失其內容,其內容將被目的對象占有。移動操作的發生的時候,是當移動值的對象是未命名的對象的時候。這裡未命名的對象就是那些臨時變數,甚至都不會有名稱。典型的未命名對象就是函數的返回值或者類型轉換的對象。使用臨時對象的值初始化另一個對象值,不會要求對對象的複製:因為臨時對象不會有其它使用,因而,它的值可以被移動到目的對象。做到這些,就要使用移動構造函數和移動賦值:當使用一個臨時變數對對象進行構造初始化的時候,調用移動構造函數。類似的,使用未命名的變數的值賦給一個對象時,調用移動賦值操作。
移動操作的概念對對象管理它們使用的存儲空間很有用的,諸如對象使用new和delete分配記憶體的時候。在這類對象中,拷貝和移動是不同的操作:從A拷貝到B意味著,B分配了新記憶體,A的整個內容被拷貝到為B分配的新記憶體上。
而從A移動到B意味著分配給A的記憶體轉移給了B,沒有分配新的記憶體,它僅僅包含簡單地拷貝指針。
看下麵的例子:
// 移動構造函數和賦值
#include <iostream>
#include <string>
using namespace std;
class Example6 {
string* ptr;
public:
Example6 (const string& str) : ptr(new string(str)) {}
~Example6 () {delete ptr;}
// 移動構造函數,參數x不能是const Pointer&& x,
// 因為要改變x的成員數據的值;
// C++98不支持,C++0x(C++11)支持
Example6 (Example6&& x) : ptr(x.ptr)
{
x.ptr = nullptr;
}
// move assignment
Example6& operator= (Example6&& x)
{
delete ptr;
ptr = x.ptr;
x.ptr=nullptr;
return *this;
}
// access content:
const string& content() const {return *ptr;}
// addition:
Example6 operator+(const Example6& rhs)
{
return Example6(content()+rhs.content());
}
};
int main () {
Example6 foo("Exam"); // 構造函數
// Example6 bar = Example6("ple"); // 拷貝構造函數
Example6 bar(move(foo)); // 移動構造函數
// 調用move之後,foo變為一個右值引用變數,
// 此時,foo所指向的字元串已經被"掏空",
// 所以此時不能再調用foo
bar = bar+bar; // 移動賦值,在這兒"="號右邊的加法操作,
// 產生一個臨時值,即一個右值
// 所以此時調用移動賦值語句
cout << "foo's content: " << foo.content() << '\n';
return 0;
}
執行結果:
foo's content: Example
虛函數
C++中的虛函數的作用主要是實現了多態的機制。關於多態,簡而言之就是用父類型的指針指向其子類的實例,然後通過父類的指針調用實際子類的成員函數。這種技術可以讓父類的指針有“多種形態”,這是一種泛型技術。如果調用非虛函數,則無論實際對象是什麼類型,都執行基類類型所定義的函數。非虛函數總是在編譯時根據調用該函數的對象,引用或指針的類型而確定。如果調用虛函數,則直到運行時才能確定調用哪個函數,運行的虛函數是引用所綁定或指針所指向的對象所屬類型定義的版本。虛函數必須是基類的非靜態成員函數。虛函數的作用是實現動態聯編,也就是在程式的運行階段動態地選擇合適的成員函數,在定義了虛函數後,可以在基類的派生類中對虛函數重新定義,在派生類中重新定義的函數應與虛函數具有相同的形參個數和形參類型。以實現統一的介面,不同定義過程。如果在派生類中沒有對虛函數重新定義,則它繼承其基類的虛函數。
class Person{
public:
//虛函數
virtual void GetName(){
cout<<"PersonName:xiaosi"<<endl;
};
};
class Student:public Person{
public:
void GetName(){
cout<<"StudentName:xiaosi"<<endl;
};
};
int main(){
//指針
Person *person = new Student();
//基類調用子類的函數
person->GetName();//StudentName:xiaosi
}
虛函數(Virtual Function)是通過一張虛函數表(Virtual Table)來實現的。簡稱為V-Table。在這個表中,主是要一個類的虛函數的地址表,這張表解決了繼承、覆蓋的問題,保證其容真實反應實際的函數。這樣,在有虛函數的類的實例中這個表被分配在了這個實例的記憶體中,所以,當我們用父類的指針來操作一個子類的時候,這張虛函數表就顯得由為重要了,它就像一個地圖一樣,指明瞭實際所應該調用的函數。
純虛函數是在基類中聲明的虛函數,它在基類中沒有定義,但要求任何派生類都要定義自己的實現方法。在基類中實現純虛函數的方法是在函數原型後加“=0” virtualvoid GetName() =0。在很多情況下,基類本身生成對象是不合情理的。例如,動物作為一個基類可以派生出老虎、孔雀等子類,但動物本身生成對象明顯不合常理。為瞭解決上述問題,將函數定義為純虛函數,則編譯器要求在派生類中必須予以重寫以實現多態性。同時含有純虛擬函數的類稱為抽象類,它不能生成對象。這樣就很好地解決了上述兩個問題。將函數定義為純虛函數能夠說明,該函數為後代類型提供了可以覆蓋的介面,但是這個類中的函數絕不會調用。聲明瞭純虛函數的類是一個抽象類。所以,用戶不能創建類的實例,只能創建它的派生類的實例。必須在繼承類中重新聲明函數(不要後面的=0)否則該派生類也不能實例化,而且它們在抽象類中往往沒有定義。定義純虛函數的目的在於,使派生類僅僅只是繼承函數的介面。純虛函數的意義,讓所有的類對象(主要是派生類對象)都可以執行純虛函數的動作,但類無法為純虛函數提供一個合理的預設實現。所以類純虛函數的聲明就是在告訴子類的設計者,“你必須提供一個純虛函數的實現,但我不知道你會怎樣實現它”。
//抽象類
class Person{
public:
//純虛函數
virtual void GetName()=0;
};
class Student:public Person{
public:
Student(){
};
void GetName(){
cout<<"StudentName:xiaosi"<<endl;
};
};
int main(){
Student student;
}
★構造函數不能是虛函數,析構函數可以
假如某個類的構造函數是虛函數,那麼想要生成該類對象,就必須調用構造函數去構造該對象,但是由於構造函數是虛函數,想要調用該構造函數必須先去類的虛表中找到該子類的虛函數的入口地址,但是想要找到虛表,必須先有對象,因為對象的開始四個位元組存放了指向虛表的指針,而不調用構造函數,又無法生成對象,所以矛盾了。
★關於虛表的一些思考
每個擁有虛函數的類都有一個虛表,父類有,子類也有,而每個這些類生成的每個對象的開始四個位元組存放了指向本類虛表的指針,並且一個類的所有對象共用本類的虛表,只需要通過開始的四個位元組去找本類的續表即可,虛表中存放了本類虛函數的地址;比如基類虛表存放了函數f的地址為a,而子類使用虛函數重載了f,子類虛表中函數f的地址為b,覆蓋了父類的地址,那麼Base *ptr = new Child(); 這個ptr指針類型是父類,但是它真正指向記憶體是一個子類,所以當ptr→f時,發現f是虛函數,首先從記憶體的前四個位元組中的指針去找虛表,由於真正占有記憶體的是子類,所以指針指向子類的續表,子類虛表中,f函數的地址是b,所以調用的是b。
虛繼承
虛繼承是解決C++多重繼承問題的一種手段,從不同途徑繼承來的同一基類,會在子類中存在多份拷貝。這將存在兩個問題:其一,浪費存儲空間;第二,存在二義性問題,通常可以將派生類對象的地址賦值給基類對象,實現的具體方式是,將基類指針指向繼承類(繼承類有基類的拷貝)中的基類對象的地址,但是多重繼承可能存在一個基類的多份拷貝,這就出現了二義性。虛繼承可以解決多種繼承前面提到的兩個問題。
#include<iostream>
using namespace std;
class A{
public:
int _a;
};
class B :virtual public A
{
public:
int _b;
};
class C :virtual public A
{
public:
int _c;
};
class D :public B, public C
{
public:
int _d;
};
//菱形繼承和菱形虛繼承的對象模型
int main()
{
D d;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
cout << sizeof(D) << endl;
return 0;
}
分別從菱形繼承和虛繼承來分析:
菱形繼承中A在B,C,D,中各有一份,虛繼承中,A共用。
上面的虛繼承表實際上是一個指針數組。B、C實際上是虛基表指針,指向虛基表。
虛基表:存放相對偏移量,用來找虛基類。
請問構造函數中的能不能調用虛方法
不要在構造函數中調用虛方法,從語法上講,調用完全沒有問題,但是從效果上看,往往不能達到需要的目的。派生類對象構造期間進入基類的構造函數時,對象類型變成了基類類型,而不是派生類類型。同樣,進入基類析構函數時,對象也是基類類型。所以,虛函數始終僅僅調用基類的虛函數(如果是基類調用虛函數),不能達到多態的效果,所以放在構造函數中是沒有意義的,而且往往不能達到本來想要的效果。
★請問拷貝構造函數的參數是什麼傳遞方式,為什麼
-
拷貝構造函數的參數必須使用引用傳遞。
-
如果拷貝構造函數中的參數不是一個引用,即形如CClass(const CClass c_class),那麼就相當於採用了傳值的方式(pass-by-value),而傳值的方式會調用該類的拷貝構造函數,從而造成無窮遞歸地調用拷貝構造函數。因此拷貝構造函數的參數必須是一個引用。
需要澄清的是,傳指針其實也是傳值,如果上面的拷貝構造函數寫成CClass(const CClass* c_class),也是不行的。事實上,只有傳引用不是傳值外,其他所有的傳遞方式都是傳值。
簡述一下虛析構函數,什麼作用
-
虛析構函數,是將基類的析構函數聲明為virtual,舉例如下:
class TimeKeeper { public: TimeKeeper() {} virtual ~TimeKeeper() {} };
-
虛析構函數的主要作用是防止記憶體泄露。
定義一個基類的指針p,在delete p時,如果基類的析構函數是虛函數,這時只會看p所賦值的對象,如果p賦值的對象是派生類的對象,就會調用派生類的析構函數(毫無疑問,在這之前也會先調用基類的構造函數,在調用派生類的構造函數,然後調用派生類的析構函數,基類的析構函數,所謂先構造的後釋放);如果p賦值的對象是基類的對象,就會調用基類的析構函數,這樣就不會造成記憶體泄露。
如果基類的析構函數不是虛函數,在delete p時,調用析構函數時,只會看指針的數據類型,而不會去看賦值的對象,這樣就會造成記憶體泄露。
★仿函數
-
仿函數(functor)又稱為函數對象(function object)是一個能行使函數功能的類。仿函數的語法幾乎和我們普通的函數調用一樣,不過作為仿函數的類,都必須重載operator()運算符,舉個例子:
class Func{ public: void operator() (const string& str) const { cout<<str<<endl; } }; Func myFunc; myFunc("helloworld!"); >>>helloworld!
-
仿函數既能想普通函數一樣傳入給定數量的參數,還能存儲或者處理更多我們需要的有用信息。我們可以舉個例子:
假設有一個
vector<string>
,你的任務是統計長度小於5的string的個數,如果使用count_if
函數的話,你的代碼可能長成這樣:bool LengthIsLessThanFive(const string& str) { return str.length()<5; } int res=count_if(vec.begin(), vec.end(), LengthIsLessThanFive);
其中
count_if
函數的第三個參數是一個函數指針,返回一個bool類型的值。一般的,如果需要將特定的閾值長度也傳入的話,我們可能將函數寫成這樣:bool LenthIsLessThan(const string& str, int len) { return str.length()<len; }
這個函數看起來比前面一個版本更具有一般性,但是他不能滿足
count_if
函數的參數要求:count_if
要求的是unary function(僅帶有一個參數)作為它的最後一個參數。如果我們使用仿函數,是不是就豁然開朗了呢:class ShorterThan { public: explicit ShorterThan(int maxLength) : length(maxLength) {} bool operator() (const string& str) const { return str.length() < length; } private: const int length; };
★explicit關鍵字作用
explicit關鍵字可以關閉類構造函數的隱式轉換:
class Demo {
public:
Demo();
Demo(int a); // Demo demo = 1; 合法,等價於Demo demo(1);
Demo(int a, int b); // Demo demo = 1; 不合法,無預設值參數數量大於1
Demo(int a, int b = 2, int c = 3); // Demo demo = 1; 合法,等價於Demo demo(1, 2, 3);
}
class Demo {
public:
Demo();
explicit Demo(int a); // Demo demo = 1; 不合法,explicit關閉隱式轉換
Demo(int a, int b);
explicit Demo(int a, int b = 2, int c = 3); // Demo demo = 1; 不合法,explicit關閉隱式轉換
哪些函數不能被聲明為虛函數
常見的不不能聲明為虛函數的有:普通函數(非成員函數),靜態成員函數,內聯成員函數,構造函數,友元函數。
-
為什麼C++不支持普通函數為虛函數?
普通函數(非成員函數)只能被overload,不能被override,聲明為虛函數也沒有什麼意思,因此編譯器會在編譯時綁定函數。
-
為什麼C++不支持構造函數為虛函數?
這個原因很簡單,主要是從語義上考慮,所以不支持。因為構造函數本來就是為了明確初始化對象成員才產生的,然而virtual function主要是為了再不完全瞭解細節的情況下也能正確處理對象。另外,virtual函數是在不同類型的對象產生不同的動作,現在對象還沒有產生,如何使用virtual函數來完成你想完成的動作。
構造函數用來創建一個新的對象,而虛函數的運行是建立在對象的基礎上,在構造函數執行時,對象尚未形成,所以不能將構造函數定義為虛函數。
-
為什麼C++不支持內聯成員函數為虛函數?
其實很簡單,那內聯函數就是為了在代碼中直接展開,減少函數調用花費的代價,虛函數是為了在繼承後對象能夠準確的執行自己的動作,這是不可能統一的。(再說了,inline函數在編譯時被展開,虛函數在運行時才能動態的綁定函數)
內聯函數是在編譯時期展開,而虛函數的特性是運行時才動態聯編,所以兩者矛盾,不能定義內聯函數為虛函數。
-
為什麼C++不支持靜態成員函數為虛函數?
這也很簡單,靜態成員函數對於每個類來說只有一份代碼,所有的對象都共用這一份代碼,他也沒有要動態綁定的必要性。
靜態成員函數屬於一個類而非某一對象,沒有this指針,它無法進行對象的判別。
-
為什麼C++不支持友元函數為虛函數?
因為C++不支持友元函數的繼承,對於沒有繼承特性的函數沒有虛函數的說法。
思考:當使用類的指針調用成員函數時,普通函數由指針類型決定,而虛函數由指針指向的實際類型決定;
虛函數實現的過程是:通過對象記憶體中的虛函數指針vptr找到虛函數表vtbl,再通過vtbl中的函數指針找到對應虛函數的實現區域併進行調用。每個子類對象中只含有一個虛函數指針vptr指向基類的虛函數表。
★虛函數表裡存放的內容是什麼時候寫進去的?
-
虛函數表是一個存儲虛函數地址的數組,以NULL結尾。虛表(vftable)在編譯階段生成,對象記憶體空間開闢以後,寫入對象中的 vfptr,然後調用構造函數。即:虛表在構造函數之前寫入。
-
除了在構造函數之前寫入之外,我們還需要考慮到虛表的二次寫入機制,通過此機制讓每個對象的虛表指針都能準確的指向到自己類的虛表,為實現動多態提供支持。
類模板和模板類
-
類模板是模板的定義,不是一個實實在在的類,定義中用到通用類型參數。
-
模板類是實實在在的類定義,是類模板的實例化。類定義中參數被實際類型所代替。
★標準模板庫STL組成部分
-
容器(Container)
是一種數據結構, 如list, vector, 和deques,以模板類的方法提供。為了訪問容器中的數據,可以使用由容器類輸出的迭代器。
-
演算法(Algorithm)
是用來操作容器中的數據的模板函數。例如,STL用sort()來對一 個vector中的數據進行排序,用find()來搜索一個list中的對象, 函數本身與他們操作的數據的結構和類型無關,因此他們可以用於從簡單數組到高度複雜容器的任何數據結構上。
-
迭代器(Iterator)
提供了訪問容器中對象的方法。例如,可以使用一對迭代器指定list或vector中的一定範圍的對象。 迭代器就如同一個指針。事實上,C++ 的指針也是一種迭代器。 但是,迭代器也可以是那些定義了operator*()以及其他類似於指針的操作符方法的類對象。
-
仿函數(Function object)
仿函數又稱之為函數對象, 其實就是重載了操作符的struct,沒有什麼特別的地方。
-
適配器(Adaptor)
簡單的說就是一種介面類,專門用來修改現有類的介面,提供一中新的介面;或調用現有的函數來實現所需要的功能。主要包括3中適配器Container Adaptor、Iterator Adaptor、Function Adaptor。
-
空間配製器(Allocator)
為STL提供空間配置的系統。其中主要工作包括兩部分:
(1)對象的創建與銷毀;
(2)記憶體的獲取與釋放。
STL容器分類及原理
-
順序容器
容器並非排序的,元素的插入位置同元素的值無關。包含vector、deque、list,具體實現原理如下:
(1)vector 頭文件
動態數組。元素在記憶體連續存放。隨機存取任何元素都能在常數時間完成。在尾端增刪元素具有較佳的性能。
(2)deque 頭文件
雙向隊列。元素在記憶體連續存放。隨機存取任何元素都能在常數時間完成(僅次於vector)。在兩端增刪元素具有較佳的性能(大部分情況下是常數時間)。
(3)list 頭文件
雙向鏈表。元素在記憶體不連續存放。在任何位置增刪元素都能在常數時間完成。不支持隨機存取。
-
關聯式容器
元素是排序的;插入任何元素,都按相應的排序規則來確定其位置;在查找時具有非常好的性能;通常以平衡二叉樹的方式實現。包含set、multiset、map、multimap,具體實現原理如下:
(1)set/multiset 頭文件
set 即集合。set中不允許相同元素,multiset中允許存在相同元素。
(2)map/multimap 頭文件
map與set的不同在於map中存放的元素有且僅有兩個成員變,一個名為first,另一個名為second, map根據first值對元素從小到大排序,並可快速地根據first來檢索元素。
**註意:**map同multimap的不同在於是否允許相同first值的元素。
-
容器適配器
封裝了一些基本的容器,使之具備了新的函數功能,比如把deque封裝一下變為一個具有stack功能的數據結構。這新得到的數據結構就叫適配器。包含stack,queue,priority_queue,具體實現原理如下:
(1)stack 頭文件
棧是項的有限序列,並滿足序列中被刪除、檢索和修改的項只能是最進插入序列的項(棧頂的項)。後進先出。
(2)queue 頭文件
隊列。插入只可以在尾部進行,刪除、檢索和修改只允許從頭部進行。先進先出。
(3)priority_queue 頭文件
優先順序隊列。內部維持某種有序,然後確保優先順序最高的元素總是位於頭部。最高優先順序元素總是第一個出列。
說一下STL中迭代器的作用,有指針為何還要迭代器?
-
迭代器的作用
(1)用於指向順序容器和關聯容器中的元素
(2)通過迭代器可以讀取它指向的元素
(3)通過非const迭代器還可以修改其指向的元素
-
迭代器和指針的區別
迭代器不是指針,是類模板,表現的像指針。他只是模擬了指針的一些功能,重載了指針的一些操作符,-->、++、--等。迭代器封裝了指針,是一個”可遍歷STL( Standard Template Library)容器內全部或部分元素”的對象,本質是封裝了原生指針,是指針概念的一種提升,提供了比指針更高級的行為,相當於一種智能指針,他可以根據不同類型的數據結構來實現不同的++,--等操作。
迭代器返回的是對象引用而不是對象的值,所以cout只能輸出迭代器使用取值後的值而不能直接輸出其自身。
-
迭代器產生的原因
Iterator類的訪問方式就是把不同集合類的訪問邏輯抽象出來,使得不用暴露集合內部的結構而達到迴圈遍歷集合的效果。
容器 | 容器上的迭代器類別 |
---|---|
vector | 隨機訪問 |
deque | 隨機訪問 |
list | 雙向 |
set/multiset | 雙向 |
map/multimap | 雙向 |
stack | 不支持迭代器 |
queue | 不支持迭代器 |
priority_queue | 不支持迭代器 |
說說 STL 中 resize 和 reserve 的區別
-
首先必須弄清楚兩個概念:
(1)capacity:該值在容器初始化時賦值,指的是容器能夠容納的最大的元素的個數。還不能通過下標等訪問,因為此時容器中還沒有創建任何對象。
(2)size:指的是此時容器中實際的元素個數。可以通過下標訪問0-(size-1)範圍內的對象。
-
resize和reserve區別主要有以下幾點:
(1)resize既分配了空間,也創建了對象;reserve表示容器預留空間,但並不是真正的創建對象,需要通過insert()或push_back()等創建對象。
(2)resize既修改capacity大小,也修改size大小;reserve只修改capacity大小,不修改size大小。
(3)兩者的形參個數不一樣。 resize帶兩個參數,一個表示容器大小,一個表示初始值(預設為0);reserve只帶一個參數,表示容器預留的大小。
答案解析
問題延伸:
resize 和 reserve 既有差別,也有共同點。兩個介面的共同點是**它們都保證了vector的空間大小(capacity)最少達到它的參數所指定的大小。**下麵就他們的細節進行分析。
為實現resize的語義,resize介面做了兩個保證:
(1)保證區間[0, new_size)範圍內數據有效,如果下標index在此區間內,vector[indext]是合法的;
(2)保證區間[0, new_size)範圍以外數據無效,如果下標index在區間外,vector[indext]是非法的。
reserve只是保證vector的空間大小(capacity)最少達到它的參數所指定的大小n。在區間[0, n)範圍內,如果下標是index,vector[index]這種訪問有可能是合法的,也有可能是非法的,視具體情況而定。
說說 STL 容器動態鏈接可能產生的問題?
-
可能產生 的問題
容器是一種動態分配記憶體空間的一個變數集合類型變數。在一般的程式函數里,局部容器,參數傳遞容器,參數傳遞容器的引用,參數傳遞容器指針都是可以正常運行的,而在動態鏈接庫函數內部使用容器也是沒有問題的,但是給動態庫函數傳遞容器的對象本身,則會出現記憶體堆棧破壞的問題。
-
產生問題的原因
容器和動態鏈接庫相互支持不夠好,動態鏈接庫函數中使用容器時,參數中只能傳遞容器的引用,並且要保證容器的大小不能超出初始大小,否則導致容器自動重新分配,就會出現記憶體堆棧破壞問題。
vector 的實現原理
vector底層實現原理為一維數組(元素在空間連續存放)。
-
新增元素
Vector通過一個連續的數組存放元素,如果集合已滿,在新增數據的時候,就要分配一塊更大的記憶體,將原來的數據複製過來,釋放之前的記憶體,再插入新增的元素。插入新的數據分在最後插入push_back和通過迭代器在任何位置插入,這裡說一下通過迭代器插入,通過迭代器與第一個元素的距離知道要插入的位置,即int index=iter-begin()。這個元素後面的所有元素都向後移動一個位置,在空出來的位置上存入新增的元素。
-
刪除元素
刪除和新增差不多,也分兩種,刪除最後一個元素pop_back和通過迭代器刪除任意一個元素erase(iter)。通過迭代器刪除還是先找到要刪除元素的位置,即int index=iter-begin();這個位置後面的每個元素都想前移動一個元素的位置。同時我們知道erase不釋放記憶體只初始化成預設值。
刪除全部元素clear:只是迴圈調用了erase,所以刪除全部元素的時候,不釋放記憶體。記憶體是在析構函數中釋放的。
C++11新特性
C++新特性主要包括包含語法改進和標準庫擴充兩個方面,主要包括以下11點:
-
語法的改進
(1)統一的初始化方法
(2)成員變數預設初始化
(3)auto關鍵字 用於定義變數,編譯器可以自動判斷的類型(前提:定義一個變數時對其進行初始化)
(4)decltype 求表達式的類型
(5)智能指針 shared_ptr
(6)空指針 nullptr(原來NULL)
(7)基於範圍的for迴圈
(8)右值引用和move語義 讓程式員有意識減少進行深拷貝操作
-
標準庫擴充(往STL里新加進一些模板類,比較好用)
(9)無序容器(哈希表) 用法和功能同map一模一樣,區別在於哈希表的效率更高
(10)正則表達式 可以認為正則表達式實質上是一個字元串,該字元串描述了一種特定模式的字元串
(11)Lambda表達式
(1)統一的初始化方法
C++98/03 可以使用初始化列表(initializer list)進行初始化,但是這種初始化方式的適用性非常狹窄,只有數組和結構體可以使用初始化列表。在 C++11 中,初始化列表的適用性被大大增加了,它現在可以用於任何類型對象的初始化。
(2)成員變數預設初始化
(3)auto關鍵字
(4)decltype求表達式的類型
(5)智能指針
和 unique_ptr、weak_ptr 不同之處在於,多個 shared_ptr 智能指針可以共同使用同一塊堆記憶體。並且,由於該類型智能指針在實現上採用的是引用計數機制,即便有一個 shared_ptr 指針放棄了堆記憶體的“使用權”(引用計數減 1),也不會影響其他指向同一堆記憶體的 shared_ptr 指針(只有引用計數為 0 時,堆記憶體才會被自動釋放)。
#include <iostream>
#include <memory>
using namespace std;
int main()
{
//構建 2 個智能指針
std::shared_ptr<int> p1(new int(10));
std::shared_ptr<int> p2(p1);
//輸出 p2 指向的數據
cout << *p2 << endl;
p1.reset();//引用計數減 1,p1為空指針
if (p1) {
cout << "p1 不為空" << endl;
}
else {
cout << "p1 為空" << endl;
}
//以上操作,並不會影響 p2
cout << *p2 << endl;
//判斷當前和 p2 同指向的智能指針有多少個
cout << p2.use_count() << endl;
return 0;
}
/* 程式運行結果:
10
p1 為空
10
1
*/
(6)空指針nullptr
nullptr 是 nullptr_t 類型的右值常量,專用於初始化空類型指針。nullptr_t 是 C++11 新增加的數據類型,可稱為“指針空值類型”。也就是說,nullpter 僅是該類型的一個實例對象(已經定義好,可以直接使用),如果需要我們完全定義出多個同 nullptr 完全一樣的實例對象。值得一提的是,nullptr 可以被隱式轉換成任意的指針類型。例如:
int * a1 = nullptr;
char * a2 = nullptr;
double * a3 = nullptr;
顯然,不同類型的指針變數都可以使用 nullptr 來初始化,編譯器分別將 nullptr 隱式轉換成 int、char 以及 double* 指針類型。另外,通過將指針初始化為 nullptr,可以很好地解決 NULL 遺留的問題,比如:
#include <iostream>
using namespace std;
void isnull(void *c){
cout << "void*c" << endl;
}
void isnull(int n){
cout << "int n" << endl;
}
int main() {
isnull(NULL);
isnull(nullptr);
return 0;
}
/* 程式運行結果:
int n
void*c
*/
(7)基於範圍的for迴圈
(8)右值引用和move語義
C++11 標準新引入了另一種引用方式,稱為右值引用,用 "&&" 表示。
需要註意的,和聲明常量左值引用一樣,右值引用也必須立即進行初始化操作,且只能使用右值進行初始化,比如:
int num = 10;
//int && a = num; //右值引用不能初始化為左值
int && a = 10;
和常量左值引用不同的是,右值引用還可以對右值進行修改。例如:
int && a = 10;
a = 100;
cout << a << endl;
/* 程式運行結果:
100
*/
另外值得一提的是,C++ 語法上是支持定義常量右值引用的,例如:
const int&& a = 10;//編譯器不會報錯
但這種定義出來的右值引用並無實際用處。一方面,右值引用主要用於移動語義和完美轉發,其中前者需要有修改右值的許可權;其次,常量右值引用的作用就是引用一個不可修改的右值,這項工作完全可以交給常量左值引用完成。
move 本意為 "移動",但該函數並不能移動任何數據,它的功能很簡單,就是將某個左值強制轉化為右值。基於 move() 函數特殊的功能,其常用於實現移動語義。move() 函數的用法也很簡單,其語法格式如下:
move( arg ) //其中,arg 表示指定的左值對象。該函數會返回 arg 對象的右值形式。
//程式實例
#include <iostream>
using namespace std;
class first {
public:
first() :num(new int(0)) {
cout << "construct!" << endl;
}
//移動構造函數
first(first &&d) :num(d.num) {
d.num = NULL;
cout << "first move construct!" << endl;
}
public: //這裡應該是 private,使用 public 是為了更方便說明問題
int *num;
};
class second {
public:
second() :fir() {}
//用 first 類的移動構造函數初始化 fir
second(second && sec) :fir(move(sec.fir)) {
cout << "second move construct" << endl;
}
public: //這裡也應該是 private,使用 public 是為了更方便說明問題
first fir;
};
int main() {
second oth;
second oth2 = move(oth);
//cout << *oth.fir.num << endl; //程式報運行時錯誤
return 0;
}
/* 程式運行結果:
construct!
first move construct!
second move construct
*/
(9)無序容器(哈希表)
無序容器 | 功能 |
---|---|
unordered_map | 存儲鍵值對 <key, value> 類型的元素,其中各個鍵值對鍵的值不允許重覆,且該容器中存儲的鍵值對是無序的。 |
unordered_multimap | 和 unordered_map 唯一的區別在於,該容器允許存儲多個鍵相同的鍵值對。 |
unordered_set | 不再以鍵值對的形式存儲數據,而是直接存儲數據元素本身(當然也可以理解為,該容器存儲的全部都是鍵 key 和值 value 相等的鍵值對,正因為它們相等,因此只存儲 value 即可)。另外,該容器存儲的元素不能重覆,且容器內部存儲的元素也是無序的。 |
unordered_multiset | 和 unordered_set 唯一的區別在於,該容器允許存儲值相同的元素。 |
(10)正則表達式
符號 | 意義 |
---|---|
^ | 匹配行的開頭 |
$ | 匹配行的結尾 |
. | 匹配任意單個字元 |
[…] | 匹配[]中的任意一個字元 |
(…) | 設定分組 |
\ | 轉義字元 |
\d | 匹配數字[0-9] |
\D | \d 取反 |
\w | 匹配字母[a-z],數字,下劃線 |
\W | \w 取反 |
\s | 匹配空格 |
\S | \s 取反 |
+ | 前面的元素重覆1次或多次 |
* | 前面的元素重覆任意次 |
? | 前面的元素重覆0次或1次 |
{n} | 前面的元素重覆n次 |
{n,} | 前面的元素重覆至少n次 |
{n,m} | 前面的元素重覆至少n次,至多m次 |
| | 邏輯或 |
(11)Lambda匿名函數
int num[4] = {4, 3, 2, 1};
sort(num, num + 4, [=](int x, int y) -> bool {return x < y;});
weak_ptr 能不能知道對象計數為 0,為什麼?
weak_ptr是一種不控制對象生命周期的智能指針,它指向一個shared_ptr管理的對象。進行該對象管理的是那個引用的shared_ptr。weak_ptr只是提供了對管理 對象的一個訪問手段。weak_ptr設計的目的只是為了配合shared_ptr而引入的一種智能指針,配合shared_ptr工作,它只可以從一個shared_ptr或者另一個weak_ptr對象構造,它的構造和析構不會引起計數的增加或減少。
weak_ptr 如何解決 shared_ptr 的迴圈引用問題?
為瞭解決迴圈引用導致的記憶體泄漏,引入了弱指針weak_ptr,weak_ptr的構造函數不會修改引用計數的值,從而不會對對象的記憶體進行管理,其類似一個普通指針,但是不會指向引用計數的共用記憶體,但是可以檢測到所管理的對象是否已經被釋放,從而避免非法訪問。
shared_ptr線程安全性
多線程環境下,調用不同shared_ptr實例的成員函數是不需要額外的同步手段的,即使這些shared_ptr擁有的是同樣的對象。但是如果多線程訪問(有寫操作)同一個shared_ptr,則需要同步,否則就會有race condition 發生。也可以使用 shared_ptr overloads of atomic functions來防止race condition的發生。
多個線程同時讀同一個shared_ptr對象是線程安全的,但是如果是多個線程對同一個shared_ptr對象進行讀和寫,則需要加鎖。
多線程讀寫shared_ptr所指向的同一個對象,不管是相同的shared_ptr對象,還是不同的shared_ptr對象,也需要加鎖保護。
智能指針有沒有記憶體泄露的情況
智能指針發生記憶體泄露的情況
當兩個對象同時使用一個shared_ptr成員變數指向對方,會造成迴圈引用,使引用計數失效,從而導致記憶體泄露。
智能指針的記憶體泄漏如何解決?
為瞭解決迴圈引用導致的記憶體泄漏,引入了弱指針weak_ptr,weak_ptr的構造函數不會修改引用計數的值,從而不會對對象的記憶體進行管理,其類似一個普通指針,但是不會指向引用計數的共用記憶體,但是可以檢測到所管理的對象是否已經被釋放,從而避免非法訪問。
★C++11 中四種類型轉換
C++中四種類型轉換分別為const_cast、static_cast、dynamic_cast、reinterpret_cast,四種轉換功能分別如下:
-
const_cast
將const變數轉為非const
-
static_cast
最常用,可以用於各種隱式轉換,比如非const轉const,static_cast可以用於類向上轉換,但向下轉換能成功但是不安全。
-
dynamic_cast
只能用於含有虛函數的類轉換,用於類向上和向下轉換,只能轉換指針或引用類型
向上轉換:指子類向基類轉換。
向下轉換:指基類向子類轉換。
這兩種轉換,子類包含父類,當父類轉換成子類時可能出現非法記憶體訪問的問題。
dynamic_cast通過判斷變數運行時類型和要轉換的類型是否相同來判斷是否能夠進行向下轉換。類型可以是指針,引用,void*。dynamic_cast可以做類之間上下轉換,向上轉換時無條件的,向下轉換的時候會進行類型檢查,類型相等成功轉換,類型不等轉換失敗。運用RTTI技術,RTTI是”Runtime Type Information”的縮寫,意思是運行時類型信息,它提供了運行時確定對象類型的方法。在c++層面主要體現在dynamic_cast和typeid,vs中虛函數表的-1位置存放了指向type_info的指針,對於存在虛函數的類型,dynamic_cast和typeid都會去查詢type_info。
-
reinterpret_cast
reinterpret_cast可以做任何類型的轉換,不過不對轉換結果保證,容易出問題。
auto和const的結合使用
(1) auto 與 const 結合的用法
a. 當類型不為引用時,auto 的推導結果將不保留表達式的 const 屬性;
b. 當類型為引用時,auto 的推導結果將保留表達式的 const 屬性。
(2)程式實例如下
int x = 0;
const auto n = x; //n 為 const int ,auto 被推導為 int
auto f = n; //f 為 const int,auto 被推導為 int(const 屬性被拋棄)
const auto &r1 = x; //r1 為 const int& 類型,auto 被推導為 int
auto &r2 = r1; //r1 為 const int& 類型,auto 被推導為 const int 類型
C++11可變參數模板
還是看C++Primer吧
★Linux中查看進程運行狀態的指令、查看記憶體使用情況的指令、tar解壓文件的參數。
-
查看進程運行狀態的指令:ps命令。“ps -aux | grep PID”,用來查看某PID進程狀態
-
查看記憶體使用情況的指令:free命令。“free -m”,命令查看記憶體使用情況。
-
tar解壓文件的參數:
五個命令中必選一個
-c: 建立壓縮檔案
-x:解壓
-t:查看內容
-r:向壓縮歸檔文件末尾追加文件
-u:更新原壓縮包中的文件
這幾個參數是可選的
-z:有gzip屬性的
-j:有bz2屬性的
-Z:有compress屬性的
-v:顯示所有過程
-O:將文件解開到標準輸出
★文件許可權怎麼修改
Linux文件的基本許可權就有九個,分別是owner/group/others三種身份各有自己的read/write/execute許可權
修改許可權指令:chmod
答案解析
舉例:文件的許可權字元為 -rwxrwxrwx 時,這九個許可權是三個三個一組。其中,我們可以使用數字來代表各個許可權。
各許可權的分數對照如下:
r | w | x |
---|---|---|
4 | 2 | 1 |
每種身份(owner/group/others)各自的三個許可權(r/w/x)分數是需要累加的,
例如當許可權為: [-rwxrwx---] ,則分數是:
owner = rwx = 4+2+1 = 7
group = rwx = 4+2+1 = 7
others= --- = 0+0+0 = 0
所以我們設定許可權的變更時,該文件的許可權數字就是770!變更許可權的指令chmod的語法是這樣的:
[root@www ~]# chmod [-R] xyz 文件或目錄
選項與參數:
xyz : 就是剛剛提到的數字類型的許可權屬性,為 rwx 屬性數值的相加。
-R : 進行遞歸(recursive)的持續變更,亦即連同次目錄下的所有文件都會變更
# chmod 770 douya.c //即修改douya.c文件的許可權為770
★說說常用的Linux命令
-
cd命令:用於切換當前目錄
-
ls命令:查看當前文件與目錄
-
grep命令:該命令常用於分析一行的信息,若當中有我們所需要的信息,就將該行顯示出來,該命令通常與管道命令一起使用,用於對一些命令的輸出進行篩選加工。
-
cp命令:複製命令
-
mv命令:移動文件或文件夾命令
-
rm命令:刪除文件或文件夾命令
-
ps命令:查看進程情況
-
kill命令:向進程發送終止信號
-
tar命令:對文件進行打包,調用gzip或bzip對文件進行壓縮或解壓
-
cat命令:查看文件內容,與less、more功能相似
-
top命令:可以查看操作系統的信息,如進程、CPU占用率、記憶體信息等
-
pwd命令:命令用於顯示工作目錄。
★說說軟鏈接和硬鏈接的區別。
-
定義不同
軟鏈接又叫符號鏈接,這個文件包含了另一個文件的路徑名。可以是任意文件或目錄,可以鏈接不同文件系統的文件。
硬鏈接就是一個文件的一個或多個文件名。把文件名和電腦文件系統使用的節點號鏈接起來。因此我們可以用多個文件名與同一個文件進行鏈接,這些文件名可以在同一目錄或不同目錄。
-
限制不同
硬鏈接只能對已存在的文件進行創建,不能交叉文件系統進行硬鏈接的創建;
軟鏈接可對不存在的文件或目錄創建軟鏈接;可交叉文件系統;
-
創建方式不同
硬鏈接不能對目錄進行創建,只可對文件創建;
軟鏈接可對文件或目錄創建;
-
影響不同
刪除一個硬鏈接文件並不影響其他有相同 inode 號的文件。
刪除軟鏈接並不影響被指向的文件,但若被指向的原文件