C語言與C++面試知識總結

2021-02-19 CPP開發者

這是一篇 C 語言與 C++面試知識點總結的文章,如果你覺得文章對你有幫助,點個再看,分享給更多的人。

const 作用

修飾指針,分為指向常量的指針(pointer to const)和自身是常量的指針(常量指針,const pointer);

修飾引用,指向常量的引用(reference to const),用於形參類型,即避免了拷貝,又避免了函數對值的修改;

const 的指針與引用指向常量的指針(pointer to const)自身是常量的指針(常量指針,const pointer)指向常量的引用(reference to const)沒有 const reference,因為引用本身就是 const pointer

(為了方便記憶可以想成)被 const 修飾(在 const 後面)的值不可改變,如下文使用例子中的 p2、p3。

使用
// 類
class A
{
private:
const int a; // 常對象成員,只能在初始化列表賦值

public:
// 構造函數
A() : a(0) { };
A(int x) : a(x) { }; // 初始化列表

// const可用於對重載函數的區分
int getValue(); // 普通成員函數
int getValue() const; // 常成員函數,不得修改類中的任何數據成員的值
};

void function()
{
// 對象
A b; // 普通對象,可以調用全部成員函數、更新常成員變量
const A a; // 常對象,只能調用常成員函數
const A *p = &a; // 指針變量,指向常對象
const A &q = a; // 指向常對象的引用

// 指針
char greeting[] = "Hello";
char* p1 = greeting; // 指針變量,指向字符數組變量
const char* p2 = greeting; // 指針變量,指向字符數組常量(const 後面是 char,說明指向的字符(char)不可改變)
char* const p3 = greeting; // 自身是常量的指針,指向字符數組變量(const 後面是 p3,說明 p3 指針自身不可改變)
const char* const p4 = greeting; // 自身是常量的指針,指向字符數組常量
}

// 函數
void function1(const int Var); // 傳遞過來的參數在函數內不可變
void function2(const char* Var); // 參數指針所指內容為常量
void function3(char* const Var); // 參數指針為常量
void function4(const int& Var); // 引用參數在函數內為常量

// 函數返回值
const int function5(); // 返回一個常數
const int* function6(); // 返回一個指向常量的指針變量,使用:const int *p = function6();
int* const function7(); // 返回一個指向變量的常指針,使用:int* const p = function7();

static 作用

修飾普通變量,修改變量的存儲區域和生命周期,使變量存儲在靜態區,在 main 函數運行前就分配了空間,如果有初始值就用初始值初始化它,如果沒有初始值系統用默認值初始化它。

修飾普通函數,表明函數的作用範圍,僅在定義該函數的文件內才能使用。在多人開發項目時,為了防止與他人命名空間裡的函數重名,可以將函數定位為 static。

修飾成員變量,修飾成員變量使所有的對象只保存一個該變量,而且不需要生成對象就可以訪問該成員。

修飾成員函數,修飾成員函數使得不需要生成對象就可以訪問該函數,但是在 static 函數內不能訪問非靜態成員。

this 指針

this 指針是一個隱含於每一個非靜態成員函數中的特殊指針。它指向調用該成員函數的那個對象。

當對一個對象調用成員函數時,編譯程序先將對象的地址賦給 this 指針,然後調用成員函數,每次成員函數存取數據成員時,都隱式使用 this 指針。

當一個成員函數被調用時,自動向它傳遞一個隱含的參數,該參數是一個指向這個成員函數所在的對象的指針。

this 指針被隱含地聲明為: ClassName const this,這意味著不能給 this 指針賦值;在 ClassName 類的 const 成員函數中,this 指針的類型為:const ClassName const,這說明不能對 this 指針所指向的這種對象是不可修改的(即不能對這種對象的數據成員進行賦值操作);

this 並不是一個常規變量,而是個右值,所以不能取得 this 的地址(不能 &this)。

在以下場景中,經常需要顯式引用 this 指針:

inline 內聯函數 特徵編譯器一般不內聯包含循環、遞歸、switch 等複雜操作的內聯函數;在類聲明中定義的函數,除了虛函數的其他函數都會自動隱式地當成內聯函數。使用

inline 使用

// 聲明1(加 inline,建議使用)
inline int functionName(int first, int second,...);

// 聲明2(不加 inline)
int functionName(int first, int second,...);

// 定義
inline int functionName(int first, int second,...) {/****/};

// 類內定義,隱式內聯
class A {
int doA() { return 0; } // 隱式內聯
}

// 類外定義,需要顯式內聯
class A {
int doA();
}
inline int A::doA() { return 0; } // 需要顯式內聯

編譯器對 inline 函數處理步驟將 inline 函數體複製到 inline 函數調用點處;為所用 inline 函數中的局部變量分配內存空間;將 inline 函數的的輸入參數和返回值映射到調用方法的局部變量空間中;如果 inline 函數有多個返回點,將其轉變為 inline 函數代碼塊末尾的分支(使用 GOTO)。優缺點

優點

內聯函數同宏函數一樣將在被調用處進行代碼展開,省去了參數壓棧、棧幀開闢與回收,結果返回等,從而提高程序運行速度。內聯函數相比宏函數來說,在代碼展開時,會做安全檢查或自動類型轉換(同普通函數),而宏定義則不會。在類中聲明同時定義的成員函數,自動轉化為內聯函數,因此內聯函數可以訪問類的成員變量,宏定義則不能。虛函數(virtual)可以是內聯函數(inline)嗎?

Are "inline virtual" member functions ever actually "inlined"?

虛函數可以是內聯函數,內聯是可以修飾虛函數的,但是當虛函數表現多態性的時候不能內聯。內聯是在編譯器建議編譯器內聯,而虛函數的多態性在運行期,編譯器無法知道運行期調用哪個代碼,因此虛函數表現為多態性時(運行期)不可以內聯。inline virtual 唯一可以內聯的時候是:編譯器知道所調用的對象是哪個類(如 Base::who()),這隻有在編譯器具有實際對象而不是對象的指針或引用時才會發生。

虛函數內聯使用

#include <iostream>
using namespace std;
class Base
{
public:
inline virtual void who()
{
cout << "I am Base\n";
}
virtual ~Base() {}
};
class Derived : public Base
{
public:
inline void who() // 不寫inline時隱式內聯
{
cout << "I am Derived\n";
}
};

int main()
{
// 此處的虛函數 who(),是通過類(Base)的具體對象(b)來調用的,編譯期間就能確定了,所以它可以是內聯的,但最終是否內聯取決於編譯器。
Base b;
b.who();

// 此處的虛函數是通過指針調用的,呈現多態性,需要在運行時期間才能確定,所以不能為內聯。
Base *ptr = new Derived();
ptr->who();

// 因為Base有虛析構函數(virtual ~Base() {}),所以 delete 時,會先調用派生類(Derived)析構函數,再調用基類(Base)析構函數,防止內存洩漏。
delete ptr;
ptr = nullptr;

system("pause");
return 0;
}

volatile

volatile int i = 10;

volatile 關鍵字是一種類型修飾符,用它聲明的類型變量表示可以被某些編譯器未知的因素(作業系統、硬體、其它線程等)更改。所以使用 volatile 告訴編譯器不應對這樣的對象進行優化。volatile 關鍵字聲明的變量,每次訪問時都必須從內存中取出值(沒有被 volatile 修飾的變量,可能由於編譯器的優化,從 CPU 寄存器中取值)const 可以是 volatile (如只讀的狀態寄存器)assert()

斷言,是宏,而非函數。assert 宏的原型定義在 <assert.h>(C)、(C++)中,其作用是如果它的條件返回錯誤,則終止程序執行。可以通過定義 NDEBUG 來關閉 assert,但是需要在原始碼的開頭,include <assert.h> 之前。

assert() 使用

#define NDEBUG          // 加上這行,則 assert 不可用
#include <assert.h>

assert( p != NULL ); // assert 不可用

sizeof() #pragma pack(n)

設定結構體、聯合以及類成員變量以 n 字節方式對齊

#pragma pack(n) 使用

#pragma pack(push)  // 保存對齊狀態
#pragma pack(4) // 設定為 4 字節對齊

struct test
{
char m1;
double m4;
int m3;
};

#pragma pack(pop) // 恢復對齊狀態

位域

Bit mode: 2; // mode 佔 2 位

類可以將其(非靜態)數據成員定義為位域(bit-field),在一個位域中含有一定數量的二進位位。當一個程序需要向其他程序或硬體設備傳遞二進位數據時,通常會用到位域。

位域的類型必須是整型或枚舉類型,帶符號類型中的位域的行為將因具體實現而定取地址運算符(&)不能作用於位域,任何指針都無法指向類的位域extern "C" 被 extern 限定的函數或變量是 extern 類型的被 extern "C" 修飾的變量和函數是按照 C 語言方式編譯和連結的

extern "C" 的作用是讓 C++ 編譯器將 extern "C" 聲明的代碼當作 C 語言代碼處理,可以避免 C++ 因符號修飾導致代碼不能和C語言庫中的符號進行連結的問題。

extern "C" 使用
#ifdef __cplusplus
extern "C" {
#endif

void *memset(void *, int, size_t);

#ifdef __cplusplus
}
#endif

struct 和 typedef struct C 中
// c
typedef struct Student {
int age;
} S;

等價於

// c
struct Student {
int age;
};

typedef struct Student S;此時 S 等價於 struct Student,但兩個標識符名稱空間不相同。

另外還可以定義與 struct Student 不衝突的 void Student() {}。

C++ 中

由於編譯器定位符號的規則(搜索規則)改變,導致不同於C語言。

1.如果在類標識符空間定義了 struct Student {...};,使用 Student me; 時,編譯器將搜索全局標識符表,Student 未找到,則在類標識符內搜索。

即表現為可以使用 Student 也可以使用 struct Student,如下:

// cpp
struct Student {
int age;
};

void f( Student me ); // 正確,"struct" 關鍵字可省略

2.若定義了與 Student 同名函數之後,則 Student 只代表函數,不代表結構體,如下:

typedef struct Student {
int age;
} S;

void Student() {} // 正確,定義後 "Student" 只代表此函數

//void S() {} // 錯誤,符號 "S" 已經被定義為一個 "struct Student" 的別名

int main() {
Student();
struct Student me; // 或者 "S me";
return 0;
}

C++ 中 struct 和 class

總的來說,struct 更適合看成是一個數據結構的實現體,class 更適合看成是一個對象的實現體。

區別:

最本質的一個區別就是默認的訪問控制

默認的繼承訪問權限。struct 是 public 的,class 是 private 的。struct 作為數據結構的實現體,它默認的數據訪問控制是 public 的,而 class 作為對象的實現體,它默認的成員變量訪問控制是 private 的。union 聯合

聯合(union)是一種節省空間的特殊的類,一個 union 可以有多個數據成員,但是在任意時刻只有一個數據成員可以有值。當某個成員被賦值後其他成員變為未定義狀態。聯合有如下特點:

匿名 union 在定義所在作用域可直接訪問 union 成員匿名 union 不能包含 protected 成員或 private 成員union 使用
#include<iostream>

union UnionTest {
UnionTest() : i(10) {};
int i;
double d;
};

static union {
int i;
double d;
};

int main() {
UnionTest u;

union {
int i;
double d;
};

std::cout << u.i << std::endl; // 輸出 UnionTest 聯合的 10

::i = 20;
std::cout << ::i << std::endl; // 輸出全局靜態匿名聯合的 20

i = 30;
std::cout << i << std::endl; // 輸出局部匿名聯合的 30

return 0;
}

C語言實現C++類

C 實現 C++ 的面向對象特性(封裝、繼承、多態)

explicit(顯式)關鍵字 explicit 修飾構造函數時,可以防止隱式轉換和複製初始化explicit 修飾轉換函數時,可以防止隱式轉換,但 按語境轉換 除外explicit 使用
struct A
{
A(int) { }
operator bool() const { return true; }
};

struct B
{
explicit B(int) {}
explicit operator bool() const { return true; }
};

void doA(A a) {}

void doB(B b) {}

int main()
{
A a1(1);// OK:直接初始化
A a2 = 1;// OK:複製初始化
A a3{ 1 };// OK:直接列表初始化
A a4 = { 1 };// OK:複製列表初始化
A a5 = (A)1;// OK:允許 static_cast 的顯式轉換
doA(1);// OK:允許從 int 到 A 的隱式轉換
if (a1);// OK:使用轉換函數 A::operator bool() 的從 A 到 bool 的隱式轉換
bool a6(a1);// OK:使用轉換函數 A::operator bool() 的從 A 到 bool 的隱式轉換
bool a7 = a1;// OK:使用轉換函數 A::operator bool() 的從 A 到 bool 的隱式轉換
bool a8 = static_cast<bool>(a1); // OK :static_cast 進行直接初始化

B b1(1);// OK:直接初始化
B b2 = 1;// 錯誤:被 explicit 修飾構造函數的對象不可以複製初始化
B b3{ 1 };// OK:直接列表初始化
B b4 = { 1 };// 錯誤:被 explicit 修飾構造函數的對象不可以複製列表初始化
B b5 = (B)1;// OK:允許 static_cast 的顯式轉換
doB(1);// 錯誤:被 explicit 修飾構造函數的對象不可以從 int 到 B 的隱式轉換
if (b1);// OK:被 explicit 修飾轉換函數 B::operator bool() 的對象可以從 B 到 bool 的按語境轉換
bool b6(b1);// OK:被 explicit 修飾轉換函數 B::operator bool() 的對象可以從 B 到 bool 的按語境轉換
bool b7 = b1;// 錯誤:被 explicit 修飾轉換函數 B::operator bool() 的對象不可以隱式轉換
bool b8 = static_cast<bool>(b1); // OK:static_cast 進行直接初始化

return 0;
}

friend 友元類和友元函數 using using 聲明

一條 using 聲明 語句一次只引入命名空間的一個成員。它使得我們可以清楚知道程序中所引用的到底是哪個名字。如:

using namespace_name::name;

構造函數的 using 聲明

在 C++11 中,派生類能夠重用其直接基類定義的構造函數。

class Derived : Base {
public:
using Base::Base;
/* ... */
};

如上 using 聲明,對於基類的每個構造函數,編譯器都生成一個與之對應(形參列表完全相同)的派生類構造函數。生成如下類型構造函數:Derived(parms) : Base(args) { }

using 指示

using 指示 使得某個特定命名空間中所有名字都可見,這樣我們就無需再為它們添加任何前綴限定符了。如:

using namespace_name name;

儘量少使用 using 指示 汙染命名空間

一般說來,使用 using 命令比使用 using 編譯命令更安全,這是由於它只導入了指定的名稱。如果該名稱與局部名稱發生衝突,編譯器將發出指示。using編譯命令導入所有的名稱,包括可能並不需要的名稱。如果與局部名稱發生衝突,則局部名稱將覆蓋名稱空間版本,而編譯器並不會發出警告。另外,名稱空間的開放性意味著名稱空間的名稱可能分散在多個地方,這使得難以準確知道添加了哪些名稱。

using 使用

儘量少使用 using 指示

using namespace std;

應該多使用 using 聲明

int x;
std::cin >> x ;
std::cout << x << std::endl;

或者

using std::cin;
using std::cout;
using std::endl;
int x;
cin >> x;
cout << x << endl;

:: 範圍解析運算符 分類全局作用域符(::name):用於類型名稱(類、類成員、成員函數、變量等)前,表示作用域為全局命名空間類作用域符(class::name):用於表示指定類型的作用域範圍是具體某個類的命名空間作用域符(namespace::name):用於表示指定類型的作用域範圍是具體某個命名空間的:: 使用
int count = 11;         // 全局(::)的 count

class A {
public:
static int count; // 類 A 的 count(A::count)
};
int A::count = 21;

void fun()
{
int count = 31; // 初始化局部的 count 為 31
count = 32; // 設置局部的 count 的值為 32
}

int main() {
::count = 12; // 測試 1:設置全局的 count 的值為 12

A::count = 22; // 測試 2:設置類 A 的 count 為 22

fun(); // 測試 3

return 0;
}

enum 枚舉類型

定作用域的枚舉類型

enum class open_modes { input, output, append };

不限定作用域的枚舉類型

enum color { red, yellow, green };

enum { floatPrec = 6, doublePrec = 10 };

decltype

decltype 關鍵字用於檢查實體的聲明類型或表達式的類型及值分類。語法:

decltype ( expression )

decltype 使用

// 尾置返回允許我們在參數列表之後聲明返回類型
template <typename It>
auto fcn(It beg, It end) -> decltype(*beg)
{
// 處理序列
return *beg; // 返回序列中一個元素的引用
}
// 為了使用模板參數成員,必須用 typename
template <typename It>
auto fcn2(It beg, It end) -> typename remove_reference<decltype(*beg)>::type
{
// 處理序列
return *beg; // 返回序列中一個元素的拷貝
}

引用 左值引用

常規引用,一般表示對象的身份。

右值引用

右值引用就是必須綁定到右值(一個臨時對象、將要銷毀的對象)的引用,一般表示對象的值。

右值引用可實現轉移語義(Move Sementics)和精確傳遞(Perfect Forwarding),它的主要目的有兩個方面:

消除兩個對象交互時不必要的對象拷貝,節省運算存儲資源,提高效率。引用摺疊宏

宏定義可以實現類似於函數的功能,但是它終歸不是函數,而宏定義中括弧中的「參數」也不是真的參數,在宏展開的時候對 「參數」 進行的是一對一的替換。

成員初始化列表

好處

常量成員,因為常量只能初始化不能賦值,所以必須放在初始化列表裡面引用類型,引用必須在定義的時候初始化,並且不能重新賦值,所以也要寫在初始化列表裡面沒有默認構造函數的類類型,因為使用初始化列表可以不必調用默認構造函數來初始化initializer_list 列表初始化

用花括號初始化器列表初始化一個對象,其中對應構造函數接受一個 std::initializer_list 參數.

initializer_list 使用

#include <iostream>
#include <vector>
#include <initializer_list>

template <class T>
struct S {
std::vector<T> v;
S(std::initializer_list<T> l) : v(l) {
std::cout << "constructed with a " << l.size() << "-element list\n";
}
void append(std::initializer_list<T> l) {
v.insert(v.end(), l.begin(), l.end());
}
std::pair<const T*, std::size_t> c_arr() const {
return {&v[0], v.size()}; // 在 return 語句中複製列表初始化
// 這不使用 std::initializer_list
}
};

template <typename T>
void templated_fn(T) {}

int main()
{
S<int> s = {1, 2, 3, 4, 5}; // 複製初始化
s.append({6, 7, 8}); // 函數調用中的列表初始化

std::cout << "The vector size is now " << s.c_arr().second << " ints:\n";

for (auto n : s.v)
std::cout << n << ' ';
std::cout << '\n';

std::cout << "Range-for over brace-init-list: \n";

for (int x : {-1, -2, -3}) // auto 的規則令此帶範圍 for 工作
std::cout << x << ' ';
std::cout << '\n';

auto al = {10, 11, 12}; // auto 的特殊規則

std::cout << "The list bound to auto has size() = " << al.size() << '\n';

// templated_fn({1, 2, 3}); // 編譯錯誤!「 {1, 2, 3} 」不是表達式,
// 它無類型,故 T 無法推導
templated_fn<std::initializer_list<int>>({1, 2, 3}); // OK
templated_fn<std::vector<int>>({1, 2, 3}); // 也 OK
}

面向對象

面向對象程序設計(Object-oriented programming,OOP)是種具有對象概念的程序編程典範,同時也是一種程序開發的抽象方針。

面向對象特徵

面向對象三大特徵 —— 封裝、繼承、多態

封裝

把客觀事物封裝成抽象的類,並且類可以把自己的數據和方法只讓可信的類或者對象操作,對不可信的進行信息隱藏。關鍵字:public, protected, private。不寫默認為 private。

protected 成員:只允許被子類及本類的成員函數訪問private 成員:只允許被本類的成員函數、友元類或友元函數訪問繼承多態多態,即多種狀態(形態)。簡單來說,我們可以將多態定義為消息以多種形式顯示的能力。重載多態(Ad-hoc Polymorphism,編譯期):函數重載、運算符重載子類型多態(Subtype Polymorphism,運行期):虛函數參數多態性(Parametric Polymorphism,編譯期):類模板、函數模板強制多態(Coercion Polymorphism,編譯期/運行期):基本類型轉換、自定義類型轉換靜態多態(編譯期/早綁定)

函數重載

class A
{
public:
void do(int a);
void do(int a, int b);
};

動態多態(運行期期/晚綁定)虛函數:用 virtual 修飾成員函數,使其成為虛函數

注意

構造函數不能是虛函數(因為在調用構造函數時,虛表指針並沒有在對象的內存空間中,必須要構造函數調用完成後才會形成虛表指針)內聯函數不能是表現多態性時的虛函數,解釋見:虛函數(virtual)可以是內聯函數(inline)嗎?

動態多態使用

class Shape                     // 形狀類
{
public:
virtual double calcArea()
{
...
}
virtual ~Shape();
};
class Circle : public Shape // 圓形類
{
public:
virtual double calcArea();
...
};
class Rect : public Shape // 矩形類
{
public:
virtual double calcArea();
...
};
int main()
{
Shape * shape1 = new Circle(4.0);
Shape * shape2 = new Rect(5.0, 6.0);
shape1->calcArea(); // 調用圓形類裡面的方法
shape2->calcArea(); // 調用矩形類裡面的方法
delete shape1;
shape1 = nullptr;
delete shape2;
shape2 = nullptr;
return 0;
}

虛析構函數

虛析構函數是為了解決基類的指針指向派生類對象,並用基類的指針刪除派生類對象。

虛析構函數使用

class Shape
{
public:
Shape(); // 構造函數不能是虛函數
virtual double calcArea();
virtual ~Shape(); // 虛析構函數
};
class Circle : public Shape // 圓形類
{
public:
virtual double calcArea();
...
};
int main()
{
Shape * shape1 = new Circle(4.0);
shape1->calcArea();
delete shape1; // 因為Shape有虛析構函數,所以delete釋放內存時,先調用子類析構函數,再調用基類析構函數,防止內存洩漏。
shape1 = NULL;
return 0;
}

純虛函數

純虛函數是一種特殊的虛函數,在基類中不能對虛函數給出有意義的實現,而把它聲明為純虛函數,它的實現留給該基類的派生類去做。

virtual int A() = 0;

虛函數、純虛函數類裡如果聲明了虛函數,這個函數是實現的,哪怕是空實現,它的作用就是為了能讓這個函數在它的子類裡面可以被覆蓋(override),這樣的話,編譯器就可以使用後期綁定來達到多態了。純虛函數只是一個接口,是個函數的聲明而已,它要留到子類裡去實現。虛函數在子類裡面可以不重寫;但純虛函數必須在子類實現才可以實例化子類。虛函數的類用於 「實作繼承」,繼承接口的同時也繼承了父類的實現。帶純虛函數的類叫抽象類,這種類不能直接生成對象,而只有被繼承,並重寫其虛函數後,才能使用。抽象類被繼承後,子類可以繼續是抽象類,也可以是普通類。虛函數指針、虛函數表虛函數指針:在含有虛函數類的對象中,指向虛函數表,在運行時確定。虛函數表:在程序只讀數據段(.rodata section,見:目標文件存儲結構),存放虛函數指針,如果派生類實現了基類的某個虛函數,則在虛表中覆蓋原本基類的那個虛函數指針,在編譯時根據類的聲明創建。虛繼承

虛繼承用於解決多繼承條件下的菱形繼承問題(浪費存儲空間、存在二義性)。

底層實現原理與編譯器相關,一般通過虛基類指針和虛基類表實現,每個虛繼承的子類都有一個虛基類指針(佔用一個指針的存儲空間,4位元組)和虛基類表(不佔用類對象的存儲空間)(需要強調的是,虛基類依舊會在子類裡面存在拷貝,只是僅僅最多存在一份而已,並不是不在子類裡面了);當虛繼承的子類被當做父類繼承時,虛基類指針也會被繼承。

實際上,vbptr 指的是虛基類表指針(virtual base table pointer),該指針指向了一個虛基類表(virtual table),虛表中記錄了虛基類與本類的偏移地址;通過偏移地址,這樣就找到了虛基類成員,而虛繼承也不用像普通多繼承那樣維持著公共基類(虛基類)的兩份同樣的拷貝,節省了存儲空間。

虛繼承、虛函數相同之處:都利用了虛指針(均佔用類的存儲空間)和虛表(均不佔用類的存儲空間)

虛繼承

虛函數

模板類、成員模板、虛函數一個類(無論是普通類還是類模板)的成員模板(本身是模板的成員函數)不能是虛函數抽象類、接口類、聚合類

抽象類:含有純虛函數的類

接口類:僅含有純虛函數的抽象類

聚合類:用戶可以直接訪問其成員,並且具有特殊的初始化語法形式。滿足如下特點:

內存分配和管理 malloc、calloc、realloc、allocamalloc:申請指定字節數的內存。申請到的內存中的初始值不確定。calloc:為指定長度的對象,分配能容納其指定個數的內存。申請到的內存的每一位(bit)都初始化為 0。realloc:更改以前分配的內存長度(增加或減少)。當增加長度時,可能需將以前分配區的內容移到另一個足夠大的區域,而新增區域內的初始值則不確定。alloca:在棧上申請內存。程序在出棧的時候,會自動釋放內存。但是需要注意的是,alloca 不具可移植性, 而且在沒有傳統堆棧的機器上很難實現。alloca 不宜使用在必須廣泛移植的程序中。C99 中支持變長數組 (VLA),可以用來替代 alloca。malloc、free

用於分配、釋放內存

malloc、free 使用

申請內存,確認是否申請成功

char *str = (char*) malloc(100);
assert(str != nullptr);

釋放內存後指針置空

free(p);
p = nullptr;

new、deletenew / new[]:完成兩件事,先底層調用 malloc 分配了內存,然後調用構造函數(創建對象)。delete/delete[]:也完成兩件事,先調用析構函數(清理資源),然後底層調用 free 釋放空間。new 在申請內存時會自動計算所需字節數,而 malloc 則需我們自己輸入申請內存空間的字節數。

new、delete 使用

申請內存,確認是否申請成功

int main()
{
T* t = new T(); // 先內存分配 ,再構造函數
delete t; // 先析構函數,再內存釋放
return 0;
}

定位 new

定位 new(placement new)允許我們向 new 傳遞額外的地址參數,從而在預先指定的內存區域創建對象。

new (place_address) type
new (place_address) type (initializers)
new (place_address) type [size]
new (place_address) type [size] { braced initializer list }

initializers 提供一個(可能為空的)以逗號分隔的初始值列表delete this 合法嗎?

合法,但:

必須保證 this 對象是通過 new(不是 new[]、不是 placement new、不是棧上、不是全局、不是其他對象成員)分配的必須保證調用 delete this 的成員函數是最後一個調用 this 的成員函數必須保證成員函數的 delete this 後面沒有調用 this 了定義只在堆(棧)生成對象類 只能在堆上

方法:將析構函數設置為私有

原因:C++ 是靜態綁定語言,編譯器管理棧上對象的生命周期,編譯器在為類對象分配棧空間時,會先檢查類的析構函數的訪問性。若析構函數不可訪問,則不能在棧上創建對象。

能在棧上

方法:將 new 和 delete 重載為私有

原因:在堆上生成對象,使用 new 關鍵詞操作,其過程分為兩階段:第一階段,使用 new 在堆上尋找可用內存,分配給對象;第二階段,調用構造函數生成對象。將 new 操作設置為私有,那麼第一階段就無法完成,就不能夠在堆上生成對象。

智能指針 C++ 標準庫(STL)中

頭文件:#include <memory>

C++ 98
std::auto_ptr<std::string> ps (new std::string(str));

C++ 11

對於該論述,歡迎讀者查閱之前發過的文章,你是《未來世界的倖存者》麼?

3.7 分割線

可以在一行中用三個以上的減號來建立一個分隔線,同時需要在分隔線的上面空一行。如下:

Class shared_ptr 實現共享式擁有(shared ownership)概念。多個智能指針指向相同對象,該對象和其相關資源會在 「最後一個 reference 被銷毀」 時被釋放。為了在結構較複雜的情景中執行上述工作,標準庫提供 weak_ptr、bad_weak_ptr 和 enable_shared_from_this 等輔助類。Class unique_ptr 實現獨佔式擁有(exclusive ownership)或嚴格擁有(strict ownership)概念,保證同一時間內只有一個智能指針可以指向該對象。你可以移交擁有權。它對於避免內存洩漏(resource leak)——如 new 後忘記 delete ——特別有用。

shared_ptr

多個智能指針可以共享同一個對象,對象的最末一個擁有著有責任銷毀對象,並清理與該對象相關的所有資源。

支持定製型刪除器(custom deleter),可防範 Cross-DLL 問題(對象在動態連結庫(DLL)中被 new 創建,卻在另一個 DLL 內被 delete 銷毀)、自動解除互斥鎖

weak_ptr

weak_ptr 允許你共享但不擁有某對象,一旦最末一個擁有該對象的智能指針失去了所有權,任何 weak_ptr 都會自動成空(empty)。因此,在 default 和 copy 構造函數之外,weak_ptr 只提供 「接受一個 shared_ptr」 的構造函數。

可打破環狀引用(cycles of references,兩個其實已經沒有被使用的對象彼此互指,使之看似還在 「被使用」 的狀態)的問題

unique_ptr

unique_ptr 是 C++11 才開始提供的類型,是一種在異常時可以幫助避免資源洩漏的智能指針。採用獨佔式擁有,意味著可以確保一個對象和其相應的資源同一時間只被一個 pointer 擁有。一旦擁有著被銷毀或編程 empty,或開始擁有另一個對象,先前擁有的那個對象就會被銷毀,其任何相應資源亦會被釋放。

auto_ptr

被 c++11 棄用,原因是缺乏語言特性如 「針對構造和賦值」 的 std::move 語義,以及其他瑕疵。

auto_ptr 與 unique_ptr 比較

auto_ptr 可以賦值拷貝,複製拷貝後所有權轉移;unqiue_ptr 無拷貝賦值語義,但實現了move 語義;auto_ptr 對象不能管理數組(析構調用 delete),unique_ptr 可以管理數組(析構調用 delete[] );強制類型轉換運算符 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();
}

運行時類型信息 (RTTI) dynamic_casttypeidtype_id 返回一個 type_info 對象的引用如果想通過基類的指針獲得派生類的數據類型,基類必須帶有虛函數type_infotype_info 類描述編譯器在程序中生成的類型信息。此類的對象可以有效存儲指向類型的名稱的指針。type_info 類還可存儲適合比較兩個類型是否相等或比較其排列順序的編碼值。類型的編碼規則和排列順序是未指定的,並且可能因程序而異。

typeid、type_info 使用

#include <iostream>
using namespace std;

class Flyable // 能飛的
{
public:
virtual void takeoff() = 0; // 起飛
virtual void land() = 0; // 降落
};
class Bird : public Flyable // 鳥
{
public:
void foraging() {...} // 覓食
virtual void takeoff() {...}
virtual void land() {...}
virtual ~Bird(){}
};
class Plane : public Flyable // 飛機
{
public:
void carry() {...} // 運輸
virtual void takeoff() {...}
virtual void land() {...}
};

class type_info
{
public:
const char* name() const;
bool operator == (const type_info & rhs) const;
bool operator != (const type_info & rhs) const;
int before(const type_info & rhs) const;
virtual ~type_info();
private:
...
};

void doSomething(Flyable *obj) // 做些事情
{
obj->takeoff();

cout << typeid(*obj).name() << endl; // 輸出傳入對象類型("class Bird" or "class Plane")

if(typeid(*obj) == typeid(Bird)) // 判斷對象類型
{
Bird *bird = dynamic_cast<Bird *>(obj); // 對象轉化
bird->foraging();
}

obj->land();
}

int main(){
Bird *b = new Bird();
doSomething(b);
delete b;
b = nullptr;
return 0;
}

相關焦點

  • C語言,C++,C ,Java之間的關係
    C語言,C++,C#,Java,這幾種語言,應該說是當前最流行,也是最基礎的計算機語言。是不是有些人看著會頭大,大腦會不叫混亂,一個計算機怎麼會有那麼的的語言呢?看著就頭大。現在,小編先來給大家說下計算機語言的發張,一臺計算機最本質的語言是機器語言,由01010101的代碼組成,CPU處理的也是由由010101的代碼組成的數據。但是,這種語言太簡單了,不好理解。就來個數字組成的語言,可以用來表達一句話,一個數字,圖像,字母......也許只有計算機可以理解,反正小編是不知道什麼意思。
  • C 語言會比 C++ 快?
    和面向過程的 C 語言相比,其繼承者 C++ 不僅可以進行 C 語言的過程化程序設計,還可以進行以繼承和多態為特點的面向對象的程序設計。要論兩者上手的難易度,對此,有網友評價道,學好 C 只要 1 年,而學好 C++ 需要的可能不止 10 年。
  • C/C++常見面試題整理
    前段時間有不少同學在後臺問是否有C/C++相關面試題,本人近半年基本上都是在C/C++開發,我也在不斷學習和總結著,C/C++有一些非常有意思的點
  • python+C、C++混合編程的應用
    排序說明不了語言的好壞,反應的不過是某個軟體開發領域的熱門程度。語言的發展不是越來越common,而是越來越專注領域。有的語言專注於簡單高效,比如python,內建的list,dict結構比c/c++易用太多,但同樣為了安全、易用,語言也犧牲了部分性能。
  • 九大程式語言優缺點第四期:c++
    上一期給大家介紹了C語言,理所應當的本期給大家了解下C語言的超集,c++那麼接下來給大家介紹主流程式語言:C++、JavaScript、C#、Ruby、PHP以及Objective-C,對於這幾種語言,大家都能看到由其打造的頂尖應用,我們一起來了解一下吧。
  • 編程大佬總結的50點學習C+的方法,一針見血,少走彎路
    社會對於c++語言的認識越來越深,很多企業在面試程式設計師的時候都會說到c++,這就說明c++的時代來臨了,所以我們要重視c++。 下面的是學c++時要注意的。絕對經典。 1.把c++當成一門新的語言學習(和c沒啥關係!真的。)
  • 五萬字長文:C/C++ 面試知識總結
    文章出自:https://github.com/huihut/interviewC/C++ 面試知識總結這是一篇五萬字的
  • c++ 之布爾類型和引用的學習總結!
    +中的三目運算符的用法就比c語言更加高級了,來先看一個示例:int a = 1;int b = 2;( a < b) ? a : b = 3;printf("a=%d,b=%d\n",a,b);上面的三目運算符語句看起來怎麼有點奇怪,它作為左值了,一般在c語言裡面它應該是作為右值賦值給一個變量的,那這樣寫在c++中有沒有錯誤,答案肯定是沒有錯的,我們還是來看一下這種寫法在c語言中報了啥錯誤:
  • 連夜整理了28頁C語言/C++常見面試題(含答案)
    連夜整理了68道
  • 程式設計師必備的app推薦:提升技術,面試,記錄總結的平臺
    發現有趣app,專注分享高質量、有趣、有用的APP。
  • c++之重載函數學習總結
    +編譯器裡面編譯時沒有問題的,如果放在c語言編譯器裡面編譯是會報錯的:root@txp-virtual-machine:/home/txp# gcc test5.ctest5.c:8:5: error: conflicting types for 『func』 int func(int a, int b)
  • C/C++優勢究竟在哪裡?是什麼讓他們經久不衰?看看這個你就懂了
    c語言誕生於1972年,是一個通用型命令式計算機程式語言,其支持結構化編程,詞彙變量範圍與遞歸,同時也是一種能夠預防各類未預期操作的靜態類型系統,最初的目標在於構建編寫系統軟體。相較於C語言,c++誕生於1983年,緊隨c語言的步伐,c++是C語言的超集,大家所知道的C語言是面向過程的,java是面向對象的,那麼C語言為了面向對象,所以誕生出現在大家所熟知的c++,被廣泛視為大規模應用構建軟體。
  • C++之字符串類學習總結
    一、回顧c語言對字符串的實現:一般我們在c語言要實現對字符串操作的話,一般是採用字符數組或者一組函數來實現的,為啥這樣做呢,那是因為c語言裡面根本就沒有字符串類型的關鍵字;而且c語言也支持自定義類型,所以更加無法獲得字符串類型為了解決這個問題,在c++中,引入了自定義類型,而且可以通過類來完成對字符串類型的定義。
  • C+相比其他語言到底難在哪裡?
    看過程式語言排行榜的都知道,c/c++自02年以來,不管時代如何發展,其排名一直在前五以內,足見其在程式語言界的地位。編程界流行這麼一句話:c幾乎什麼都能做,c++幾乎什麼都能做好,足見其功能的強大。
  • c語言的選擇結構(一)
    例如:(一)工程師崗位面試(關係運算符和邏輯運算符)題目要求:上海一家IT企業招聘c/c++開發工程師,滿足以下任職要求者可以獲得面試機會。(1)學歷:計算機相關專業畢業,碩士及以上學歷;(2)工作年限:兩年以上c/c++開發工程師崗位從業經驗;(3)業務要求:熟練或精通c/c++和數據壓縮算法。
  • C 2 C++進階篇(1)
    首先談談筆者的水平,只學過c和數據結構,接觸過指針,對於取地址&從來沒有接觸過(因為據說是老師說不符合嚴謹的c....), python
  • 連夜整理了68道C語言/C++常見面試題(含答案)
    連夜整理了68道
  • 面試:C/C++常見庫函數實現
    (char*)dest;        char* psrc = (char*)src;        while(n--){            *pdest++ = *psrc++;        }    }    return dest;}2、void *memset(void *s, int c,
  • C語言?c+?到底先學哪個才能更好的理解編程,這些你造嗎
    最近大一新生們剛剛結束第一個學期的學習,接踵而來的問題也越來越多,不同的學校有不同的學習節奏,但是基本上都是從C語言或者c++開始學起。現在越來越多的人對於「學習C語言還有必要嗎?」這件事比較糾結。
  • 剖析C語言中a=a+++a的無聊問題
    這種純屬C語言 「二」 級的問題應該是從a+++a引申出來的吧。於是乎兄弟姐妹們開始討論它的運算結果,以及改如何理解。更有人寫出(a++)+(++a) a+(++(++a)) ((a++)++)+a這樣的東西,問應該如何計算。我表示鴨梨很大...