C++ 一把窥探OC底层的利刃

5,996 阅读18分钟

GitHub Repo:coderZsq.github.io
Follow: coderZsq · GitHub
Resume: coderzsq.github.io/coderZsq.we…

日常扯淡

作为iOS开发的菜鸡, 平日里的工作就是做业务, 调UI, 对于我们这种弱鸡玩家来说, 编程呢, 其实就是调方法, 调属性, 调库...

但光是做业务UI的工作肯定会让自己日渐乏味, 为了不重复写那些看了想吐的代码, 去年就花了点时间写了一个代码生成工具, 用于配置一键生成垃圾代码的, 这样对于菜鸡开发者也就是我来说, 只需要调调自己封装的一些UI库, 搭搭积木就完成工作了, 其他时间就可以自由支配玩点有趣的事情.

去年浑浑噩噩, 学了一堆有的没的, 什么Vue, React, Spring的调用, 但一直这样无所事事的我, 今年也想深入的学习一些什更深层次的东西, 而不仅仅只是在UI层的浅尝辄止.

然而在iOS这个领域中, 想要深入研究, C\C++, 汇编, 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\n", buf);

这个是大家都很熟悉的C的输入输出, 也就是scanfprintf. 但C语言是越界不检的, 所以这里的char buf[10]的缓冲区可能会造成写越界, 导致不安全访问.

char buf[10];
fgets(buf, 10, stdin);
printf("%s\n", 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\n", 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