C++ 一把窺探OC底層的利刃

Castie1發表於2019-03-04

GitHub Repo:coderZsq.github.io

Follow: coderZsq · GitHub

Resume: coderzsq.github.io/coderZsq.we…

日常扯淡

作為iOS開發的菜雞, 平日裡的工作就是做業務, 調UI, 對於我們這種弱雞玩家來說, 程式設計呢, 其實就是調方法, 調屬性, 調庫…

但光是做業務UI的工作肯定會讓自己日漸乏味, 為了不重複寫那些看了想吐的程式碼, 去年就花了點時間寫了一個程式碼生成工具, 用於配置一鍵生成垃圾程式碼的, 這樣對於菜雞開發者也就是我來說, 只需要調調自己封裝的一些UI庫, 搭搭積木就完成工作了, 其他時間就可以自由支配玩點有趣的事情.

去年渾渾噩噩, 學了一堆有的沒的, 什麼Vue, React, Spring的呼叫, 但一直這樣無所事事的我, 今年也想深入的學習一些什更深層次的東西, 而不僅僅只是在UI層的淺嘗輒止.

然而在iOS這個領域中, 想要深入研究, CC++, 彙編, Linux, 像三座大山一樣攔在我面前, 所以做為菜雞的我路漫漫而其修遠兮.

本文就通過學習C++的語法開始, 一點一點的剖析OC的本質, 直到世界的盡頭.

C++ 作者的建議

  1. 在C++中幾乎不需要用巨集, 用const和enum定義顯式的常量, 用inline避免函式呼叫的額外開銷, 用模板去刻畫一組函式或型別, 用namespace去避免命名衝突.
  2. 不要在你需要變數之前去宣告, 以保證你能立即對它進行初始化.
  3. 不要用malloc, new運算會做的更好.
  4. 避免使用void*, 指標算數, 聯合和強制, 大多數情況下, 強制型別轉換是設計錯誤的指示器.
  5. 儘量少用陣列和C風格的字串, 標準庫中的string和vector可以簡化程式.
  6. 更加重要的是, 試著將程式考慮為一組由類和物件表示的互相作用的概念年, 而不是一堆資料結構和一些可以撥弄的二進位制.

標準輸入輸出

我們先來看C++的標準輸入輸出:

char buf[10];
scanf("%s", buf);
printf("%s
", buf);
複製程式碼

這個是大家都很熟悉的C的輸入輸出, 也就是scanfprintf. 但C語言是越界不檢的, 所以這裡的char buf[10]的緩衝區可能會造成寫越界, 導致不安全訪問.

char buf[10];
fgets(buf, 10, stdin);
printf("%s
", buf);
複製程式碼

所以C使用了fget強制的截斷了輸入來保證, 訪問的安全.

12345678901234567
123456789
Program ended with exit code: 0
複製程式碼

以上是C語言安全標準輸入的列印日誌, 我們可以看到, 由於設定了10為輸入截斷引數, 後面就不再輸入了.

char buf[10];
cin>>buf;
cout<<buf<<endl;
複製程式碼

我們再來看看C++的輸入流cincout, 可以看到的是, cin 操作char buf[10] 同樣不安全, 也會造成寫越界.

char buf[10];
cin.getline(buf, 10);
cout<<buf<<endl;
複製程式碼

我們可以看到, cin.getline很好的解決了這個問題, 但其實作用和fgets並無二異.

string buf;
cin>>buf;
cout<<buf<<endl;
複製程式碼

然而使用string代替char buf[10] 避免char[]安全問題, 才是C++的正確開啟方式, 這下無論怎樣亂搞, 都不會有問題.

int data = 1234;
cout<<hex<<data<<endl;
cout<<oct<<data<<endl;
cout<<dec<<data<<endl;
複製程式碼

接下來, 我們來看看, C++的進位制轉換, 使用<<hex,代表16進位制, <<oct, 代表8進位制, <<dec,代表10進位制.

4d2
2322
1234
Program ended with exit code: 0
複製程式碼

使用<<hex切換進位制, 注意雖然預設是10進位制但切換其他進位制後, 預設為切換後的進位制.

int data = 1234;
cout<<data<<endl;
cout<<setw(10)<<setiosflags(ios::left)<<data<<endl;
cout<<setw(10)<<setiosflags(ios::right)<<data<<endl;
複製程式碼

使用setwsetiosflags來設定域寬和左右對齊.

#include <iomanip>
複製程式碼

在使用setwsetiosflags之前需要新增標頭檔案.

1234
1234      
      1234
Program ended with exit code: 0
複製程式碼

以上就是使用setwsetiosflags的列印結果.

int a = 12;
int b = 3;
int c = 5;    
cout<<setfill(`0`)<<setw(2)<<a<<":"<<setw(2)<<b<<":"<<setw(2)<<c<<endl;
複製程式碼

使用setfill進行填充, 這個就不多說了, 試試就知道.

float f = 1.23456;
cout<<f<<endl;
cout<<setprecision(4)<<f<<endl;
cout<<setprecision(4)<<setiosflags(ios::fixed)<<f<<endl;
複製程式碼

使用setprecision來調整浮點數的精度.

函式過載

函式過載, 會出現重名的函式, 重名的函式會根據語境來決定呼叫, 運算子過載也是一種函式過載.

void func(int a) {
    cout<<"void func(int a)"<<endl;
}
void func(float a) {
    cout<<"void func(float a)"<<endl;
}
void func(char a) {
    cout<<"void func(char a)"<<endl;
}

int main(int argc, const char * argv[]) {
    
    int a = 1;
    func(a);
    float b = 1.2;
    func(b);
    char c = `c`;
    func(c);

    return 0;
}
複製程式碼
void func(int a)
void func(float a)
void func(char a)
複製程式碼

函式過載是一種簡潔的需要, 函式返回值型別不能構成函式過載的標誌.

過載底層實現使用命名傾軋來改變函式名, 區分不同引數不同的同名函式.

#ifndef mystr_h
#define mystr_h

#include <stdio.h>

extern "C" int myStrlen(const char *s);

#endif /* mystr_h */

#include "mystr.h"
//extern "C" {
int myStrlen(const char *s) {
    int len = 0;
    while (*s) {
        len++;
        s++;
    }
    return len;
}
//}
複製程式碼

C++預設進行傾軋, 使用extern "C" 來避免傾軋造成的連結錯誤. 用來連線C的庫.

運算子過載

和上面講的相同, 運算子過載的本質其實也是一種函式過載, 相信熟悉Swift的你, 一定不會陌生.

typedef struct _pos {
    int x_;
    int y_;
} Pos;

bool operator== (Pos one, Pos another) {
    if (one.x_ == another.x_ && one.y_ == another.y_) {
        return true;
    } else {
        return false;
    }
}

int main(int argc, const char * argv[]) {
    
    Pos ps = {1, 2};
    Pos fdPs = {3, 4};
    if (ps == fdPs) {
        cout<<"=="<<endl;
    } else {
        cout<<"!="<<endl;
    }
    
    return 0;
}
複製程式碼
Pos ps = {1, 2};
Pos fdPs = {3, 4};
if (operator==(ps, fdPs)) {
    cout<<"=="<<endl;
} else {
    cout<<"!="<<endl;
}
複製程式碼

運算子過載其實也就是函式呼叫.

預設引數

OC沒有預設引數, 而C++卻有…., Swift也有這個特性.

void foo(int a = 1, int b = 2, int c = 3) {
    cout<<"====="<<endl;
    cout<<"a = "<<a<<endl;
    cout<<"b = "<<b<<endl;
    cout<<"c = "<<c<<endl;
}

int main(int argc, const char * argv[]) {
    
    foo();
    foo(2);
    foo(2, 3);
    foo(2, 3, 4);
    return 0;
}
複製程式碼
=====
a = 1
b = 2
c = 3
=====
a = 2
b = 2
c = 3
=====
a = 2
b = 3
c = 3
=====
a = 2
b = 3
c = 4
複製程式碼

預設引數和函式過載可能會產生衝突. 優先選擇預設引數的方案.

引用

引用的本質是對指標的包裝, 避免使用裸露的指標, 引用是一種宣告關係, 不開闢空間, 這裡說不開闢, 只是程式碼層面看, 實際開始會開闢空間的.

int a = 100;
int &ra = a;
cout<<"a = "<<a<<endl;
cout<<"ra = "<<ra<<endl;
cout<<"&a = "<<&a<<endl;
cout<<"&ra = "<<&ra<<endl;
複製程式碼
a = 100
ra = 100
&a = 0x7ffeefbff5cc
&ra = 0x7ffeefbff5cc
Program ended with exit code: 0
複製程式碼
void swap(int &ra, int &rb) {
    ra ^= rb;
    rb ^= ra;
    ra ^= rb;
}

int main(int argc, const char * argv[]) {
    
    int a = 10;
    int b = 20;
    swap(a, b);
    cout<<a<<"-"<<b<<endl;
    
    return 0;
}
複製程式碼
20-10
複製程式碼

傳引用等於傳作用域, 這一點熟悉OC的同學其實根本不用在意.

void swap(char **a, char **b) {
    char *t = *a;
    *a = *b;
    *b = t;
}

void swap(char * &ra, char * &rb) {
    char *t = ra;
    ra = rb;
    rb = t;
}

int main(int argc, const char * argv[]) {
    
    char *p = "china";
    char *q = "canada";
    cout<<"p = "<<p<<endl;
    cout<<"q = "<<q<<endl;
    swap(&p, &q);
    cout<<"p = "<<p<<endl;
    cout<<"q = "<<q<<endl;
    swap(p, q);
    cout<<"p = "<<p<<endl;
    cout<<"q = "<<q<<endl;

    return 0;
}
複製程式碼
p = china
q = canada
p = canada
q = china
p = china
q = canada
複製程式碼

上面是指標的引用, 並沒有引用的指標.

int * p;
int ** pp = &p;
int *** ppp = &pp;
int **** pppp = &ppp;
    
int a;
int &ra = a;
int &rb = ra;
int &rc = rb;
複製程式碼

引用為平級, 沒有指標的指標這種概念.

int arr[10] = {1, 2, 3 ,4, 5, 6, 7};
int * const & parr = arr;
for (int i = 0; i < sizeof(arr) / sizeof(int); i++) {
    cout<<parr[i]<<endl;
}
    
int (& rarr)[10] = arr;
cout<<"sizeof = "<<sizeof(rarr)<<endl;
    return 0;

複製程式碼

上面是陣列的引用, 陣列的引用呢, 在OC上就是*, 而底層原來是這樣實現的.

int foo() {
    int a = 200;
    return a;
}

int main(int argc, const char * argv[]) {
    
    const int & c = 100;
    cout<<c<<endl;

    int a = 3; int b = 5;
    const int & ret = a + b;
    cout<<ret<<endl;
    
    const int & ra = foo();
    cout<<ra<<endl;
    
    double d = 100.12;
    double & rd = d;
    const int & rd2 = d;
    cout<<rd<<endl;
    cout<<rd2<<endl;

    rd = 200.14;
    cout<<rd<<endl;
    cout<<rd2<<endl;
    
    return 0;
}
複製程式碼
100
8
200
100.12
100
200.14
100
Program ended with exit code: 0
複製程式碼

常引用, 引用的是一個暫存器常量. 和巨集在預編譯期間替換不同, 常引用在彙編期間通過暫存器替換.

void foo(int & ri, char & rc) {
    cout<<sizeof(ri)<<" "<<sizeof(rc)<<endl;
}

struct TypeC {
    char c;
};

struct TypeP {
    char * pc;
};

struct TypeR {
    char & rc;
};

void mySwap(int * pa, int * pb) {
    int t = *pa;
    *pa = *pb;
    *pb = t;
}

void mySwap(int & ra, int & rb) {
    int t = ra;
    ra = rb;
    rb = t;
}

int main(int argc, const char * argv[]) {
    
    int a; char c;
    foo(a, c);
    
    cout<<"sizeof(TypeC) = "<<sizeof(TypeC)<<endl;
    cout<<"sizeof(TypeP) = "<<sizeof(TypeP)<<endl;
    cout<<"sizeof(TypeR) = "<<sizeof(TypeR)<<endl;

    int n = 3, m = 5;
    mySwap(&n, &m);
    cout<<n<<" "<<m<<endl;
    mySwap(n, m);
    cout<<n<<" "<<m<<endl;

    return 0;
}
複製程式碼

我們等下用匯編來對比一下指標和引用之前的區別.

    0x100001060 <+0>:  pushq  %rbp
    0x100001061 <+1>:  movq   %rsp, %rbp
    0x100001064 <+4>:  movq   %rdi, -0x8(%rbp)
    0x100001068 <+8>:  movq   %rsi, -0x10(%rbp)
    0x10000106c <+12>: movq   -0x8(%rbp), %rsi
    0x100001070 <+16>: movl   (%rsi), %eax
    0x100001072 <+18>: movl   %eax, -0x14(%rbp)
    0x100001075 <+21>: movq   -0x10(%rbp), %rsi
    0x100001079 <+25>: movl   (%rsi), %eax
    0x10000107b <+27>: movq   -0x8(%rbp), %rsi
    0x10000107f <+31>: movl   %eax, (%rsi)
->  0x100001081 <+33>: movl   -0x14(%rbp), %eax
    0x100001084 <+36>: movq   -0x10(%rbp), %rsi
    0x100001088 <+40>: movl   %eax, (%rsi)
    0x10000108a <+42>: popq   %rbp
    0x10000108b <+43>: retq  
複製程式碼

指標的彙編

    0x100001090 <+0>:  pushq  %rbp
    0x100001091 <+1>:  movq   %rsp, %rbp
    0x100001094 <+4>:  movq   %rdi, -0x8(%rbp)
    0x100001098 <+8>:  movq   %rsi, -0x10(%rbp)
    0x10000109c <+12>: movq   -0x8(%rbp), %rsi
    0x1000010a0 <+16>: movl   (%rsi), %eax
    0x1000010a2 <+18>: movl   %eax, -0x14(%rbp)
    0x1000010a5 <+21>: movq   -0x10(%rbp), %rsi
    0x1000010a9 <+25>: movl   (%rsi), %eax
    0x1000010ab <+27>: movq   -0x8(%rbp), %rsi
    0x1000010af <+31>: movl   %eax, (%rsi)
->  0x1000010b1 <+33>: movl   -0x14(%rbp), %eax
    0x1000010b4 <+36>: movq   -0x10(%rbp), %rsi
    0x1000010b8 <+40>: movl   %eax, (%rsi)
    0x1000010ba <+42>: popq   %rbp
    0x1000010bb <+43>: retq   
複製程式碼

引用的彙編

引用的本質是個指標, 必須初始化, 長指標, 一經宣告不可改變. 類似於 int * const p.

new 和 delete

newdelete, 還有new[], delete[], 是用來代替mallocfree的, 兩者之間不能串用.

int * p1 = (int *)malloc(sizeof(int));
int * p2 = new int;
*p2 = 100;
cout<<*p1<<" "<<*p2<<endl;
    
int **pp1 = (int **)malloc(sizeof(int *));
int **pp2 = new int *;
pp1 = pp2;
    
struct Stu {
    float score;
    char name[30];
    char sex;
};
    
Stu * ps1 = (Stu *)malloc(sizeof(Stu));
Stu * ps2 = new Stu;
cout<<sizeof(*ps1)<<" "<<sizeof(*ps2)<<endl;
複製程式碼
0 100
36 36
複製程式碼

以上是newmalloc的比較.

float * pf1 = (float *)malloc(10 * sizeof(float));
float * pf2 = new float[10]{1.2, 3.4};
for (int i = 0; i < 10; i++) {
    cout<<pf1[i]<<" "<<pf2[i]<<endl;
}

char ** pp = new char * [10];
for (int i = 0; i < 10; i++) {
    pp[i] = "Castiel";
}
pp[10] = nullptr;
while (*pp) {
    cout<<*pp++<<endl;
}

int (* p)[5] = new int[3][5];
for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 5; j++) {
        p[i][j] = i + j;
    }
}
for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 5; j++) {
        cout<<p[i][j]<<" ";
    }
    cout<<endl;
}

int (* p2)[3][5] = new int[2][3][5];
for (int i = 0; i < 2; i++) {
    for (int j = 0; j < 3; j++) {
        for (int k = 0; k < 5; k++) {
            p2[i][j][k] = i + j + k;
        }
    }
}
複製程式碼

對於連續的空間, 也就是陣列來說, 我們可以使用new[], 來開闢堆記憶體.

0 1.2
0 3.4
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
Castiel
Castiel
Castiel
Castiel
Castiel
Castiel
Castiel
Castiel
Castiel
Castiel
0 1 2 3 4 
1 2 3 4 5 
2 3 4 5 6 
複製程式碼

上述程式碼的列印日誌.

int * p = new int;
delete p;
int ** pp = new int * [10];
delete []pp;
int (*ppp)[5] = new int[3][5];
delete []ppp;
複製程式碼

deletedelete[], 就是釋放記憶體和連續的記憶體.

char ** p = new char * [10];
for (int i = 0; i < 10; i++) {
    p[i] = new char[10];
}
for (int i = 0; i < 10; i++) {
    delete []p[i];
}
delete []p;
複製程式碼

釋放由內向外, 層級釋放.

try {
    double * pd[50];
    for (int i = 0; i< 50; i++) {
        pd[i] = new double[500000000000];
        cout<<i<<endl;
    }
} catch (bad_alloc & e) {
    cout<<"記憶體申請異常 "<<e.what()<<endl;
}
複製程式碼
... cpp
C++(37659,0x100395340) malloc: *** mach_vm_map(size=4000000000000) failed (error code=3)
*** error: can`t allocate region
*** set a breakpoint in malloc_error_break to debug
記憶體申請異常 std::bad_alloc
Program ended with exit code: 0
複製程式碼

對於堆記憶體申請失敗的異常捕獲的第一種方式, try-catch, 貌似OC中很少用到, 因為OC可以給空物件傳送訊息.

void newError( ) {
    cout<<"記憶體申請異常"<<endl;
    exit(1);
}

int main(int argc, const char * argv[]) {

    double * pd[50];
    set_new_handler(newError);
    for (int i = 0; i< 50; i++) {
        pd[i] = new double[500000000000];
        cout<<i<<endl;
    }
    return 0;
}
複製程式碼
...
C++(37705,0x100395340) malloc: *** mach_vm_map(size=4000000000000) failed (error code=3)
*** error: can`t allocate region
*** set a breakpoint in malloc_error_break to debug
記憶體申請異常
Program ended with exit code: 1
複製程式碼

第二種是使用set_new_handler回撥函式來進行捕獲.

double * pd[50];
for (int i = 0; i< 50; i++) {
    pd[i] = new (nothrow)double[500000000000];
    if (pd[i] == nullptr) {
        cout<<"記憶體申請異常 "<<" "<<__FILE__<<" "<<__func__<<" "<<__LINE__<<endl;
    }
}
複製程式碼
...
C++(37787,0x100395340) malloc: *** mach_vm_map(size=4000000000000) failed (error code=3)
*** error: can`t allocate region
*** set a breakpoint in malloc_error_break to debug
記憶體申請異常  /Users/zhushuangquan/Desktop/C++/C++/main.cpp main 19
複製程式碼

第三種則是不進行異常捕獲…., 三種情況優先使用try-catch.

inline 行內函數

inline int sqr(int x) {
    return x * x;
}

int main(int argc, const char * argv[]) {

    int i = 0;
    while (i < 5) {
        printf("%d
", sqr(i++));
    }
    return 0;
}
複製程式碼

代替巨集函式, 會在程式碼段出現多個副本, 但取決於編譯器優化, 適用函式體小並被頻繁呼叫,

強制型別轉換

儘量不要強轉, 強轉是設計不足導致的.

double d; int i;
d = static_cast<double>(i);
i = static_cast<int>(d);

d = static_cast<double>(10) / 3;
cout<<d<<endl;

void * p; int * q;
p = q;
q = static_cast<int *>(p);
複製程式碼
3.33333
Program ended with exit code: 0
複製程式碼

static_cast 隱式轉化

int * m; int n;
m = reinterpret_cast<int *>(n);
複製程式碼

reinterpret_cast 指標與數值之間進行轉換

void foo(const int & a) {
    const_cast<int &>(a) = 200;
}

int main(int argc, const char * argv[]) {

    int a;
    const int & ra = a;
    a = 100;
    cout<<a<<endl;
    const_cast<int &>(ra) = 300;
    cout<<ra<<endl;
    cout<<a<<endl;

    const int * p = &a;
    *const_cast<int *>(p) = 400;
    cout<<*p<<endl;
    
    foo(a);
    cout<<a<<endl;
    
    return 0;
}
複製程式碼

const_cast只作用與指標和引用, 去const

const int a = 100;
const int & ra = a;
    
const_cast<int &>(ra) = 200;
cout<<a<<endl;
cout<<ra<<endl;
複製程式碼
100
200
複製程式碼

對於const修飾的值, 是不能改變的,

名稱空間

對於OC是用字首, 對於Java是用包名, 對於Swift也有和C++一樣的名稱空間.

void foo() {
    cout<<"foo"<<endl;
}

int mm = 100;

int main(int argc, const char * argv[]) {

    std::cout<<::mm<<endl;
    ::foo();
    return 0;
}
複製程式碼

::全域性無名名稱空間.

namespace ONE {
    int x = 4;
}

namespace ANOTHER {
    int x = 14;
}

int main(int argc, const char * argv[]) {
    
    {
        int x = 250;
        cout<<ONE::x<<endl;
        cout<<ANOTHER::x<<endl;
        cout<<x<<endl;
    }
    
    {
        using ONE::x;
        cout<<x<<endl;
    }
    
    {
        using namespace ANOTHER;
        cout<<x<<endl;
    }
    return 0;
}
複製程式碼
4
14
250
4
14
Program ended with exit code: 0
複製程式碼

名稱空間的使用, 名稱空間只能定義在全域性.

第一種推薦使用, 第二種少用, 第三種禁用.

namespace ONE {
    int x = 4;
    namespace ANOTHER {
        int x = 14;
    }
}

int main(int argc, const char * argv[]) {

    cout<<ONE::ANOTHER::x<<endl;
    return 0;
}
複製程式碼
14
Program ended with exit code: 0
複製程式碼

名稱空間的巢狀.

namespace ONE {
    int a = 4;

}
namespace ONE {
    int b = 14;
}

int main(int argc, const char * argv[]) {

    using namespace ONE;
    cout<<a<<" "<<b<<endl;
    return 0;
}
複製程式碼

同名名稱空間自動合併.

string

字串是C++C高階的地方, 操作起來也比C簡單太多, 也比OC簡單太多, 問OC為啥那麼麻煩.

int * pi = new int(10);
cout<<pi<<endl;
cout<<*pi<<endl;

string * p = new string("Castiel");
cout<<p<<endl;
cout<<*p<<endl;

char * q = "Castiel";
cout<<q<<endl;
cout<<*q<<endl;
複製程式碼
0x100506740
10
0x10050c710
Castiel
Castiel
C
Program ended with exit code: 0
複製程式碼

雖然string是一種類, 但已經可以和int的地位相同了.

string s;
cout<<sizeof(string)<<endl;
cout<<sizeof(s)<<endl;

string s1("Castiel");
string s2 = "Castiel";
cout<<s1<<" "<<s2<<endl;

cin>>s;
cout<<s<<endl;

getline(cin, s); //解決了空格的問題
cout<<s<<endl;

string s3 = "Great Wall";
cout<<s3.size()<<endl;

string s4 = " in China";
cout<<(s3 += s4)<<endl;

string s5 = "Great Wall";
if (s3 == s5) {
    cout<<"=="<<endl;
} else {
    cout<<"!="<<endl;
}

string s6;
cout<<(s6 = s3)<<endl;

string s7 = to_string(1234);
cout<<s7<<endl;

string s8 = "123abc";
cout<<stoi(s8)<<endl;

複製程式碼
24
24
Castiel Castiel
10
Great Wall in China
!=
Great Wall in China
1234
123
Program ended with exit code: 0
複製程式碼

上述是string的基本使用, 沒啥技術含量.

class

C++中, 結構體和類的本質沒有什麼具體的區別, 只是許可權訪問上有些許不同, 而不是像以前認為的類是引用傳遞, 而結構體是值傳遞, 傳地址, 不也是值麼, 只不過能取地址… 笑.

struct Date {
    
    void init(int year = 1970, int month = 01, int day = 01) {
        _year = year;
        _month = month;
        _day = day;
    }
    
    void printDate() {
        cout<<_year<<"-"<<_month<<"-"<<_day<<endl;
    }
    
private:
    int _year;
    int _month;
    int _day;
};
複製程式碼

struct預設全部是public.

class Date {
    
    int _year;
    int _month;
    int _day;
    
public:
    void init(int year = 1970, int month = 01, int day = 01) {
        _year = year;
        _month = month;
        _day = day;
    }
    
    void printDate() {
        cout<<_year<<"-"<<_month<<"-"<<_day<<endl;
    }
};
複製程式碼

class預設全部是private.


int main(int argc, const char * argv[]) {

    Date * date = new Date;
    date->init(1992, 06, 19);
    date->printDate();
    delete date;

    Date * date2 = new Date;
    date2->init(2012, 12, 21);
    date2->printDate();
    delete date2;

    return 0;
}
複製程式碼
1992-6-19
2012-12-21
(lldb) p/x date
(Date *) $0 = 0x000000010050e9c0
(lldb) p/x date2
(Date *) $1 = 0x000000010050f2b0
(lldb) 
Program ended with exit code: 0
複製程式碼

從列印上來說, 結構體和類是一樣的, 可以說類的本質就是結構體指標, 但其實類也可以不用指標引用, 就變成了值傳遞? 又笑.

class Date {
    
private:
    int _year;
    int _month;
    int _day;
    
public:
    Date(int year = 1970, int month = 01, int day = 01);
    ~Date();
    void printDate();
};

Date::Date(int year, int month, int day)
:_year(year), _month(month), _day(day){}

Date::~Date() {
    cout<<"delete: "<<_year<<"-"<<_month<<"-"<<_day<<endl;
}

void Date::printDate() {
    cout<<_year<<"-"<<_month<<"-"<<_day<<endl;
}

int main(int argc, const char * argv[]) {
    
    Date * date = new Date(1992, 06, 19);
    date->printDate();
    delete date;
    
    Date * date2 = new Date(2012, 12, 21);
    date2->printDate();
    delete date2;
    
    return 0;
}
複製程式碼
1992-6-19
delete: 1992-6-19
2012-12-21
delete: 2012-12-21
Program ended with exit code: 0
複製程式碼

class多檔案的基本用法, 構造器, 析構器, 引數列表什麼的就不多說了.

namespace xxx {
    int a;
    void log();
}

void xxx::log() {
    a = 120;
    cout<<a<<endl;
}
複製程式碼

其實類名本質就是一個名稱空間. 怎麼又是名稱空間了呢, 再笑….

最後

更多新鮮文章可以關注並Star, 我們一起學習.

GitHub Repo:coderZsq.github.io

Follow: coderZsq · GitHub

相關文章