GeekBand C++ 第二周

2016-05-21  本文已影响51人  hui1429

7.三大函数:拷贝构造,拷贝赋值,析构

String s3(s1);//拷贝构造函数(s3刚刚出现)
String s4 = s1;//这种情况也是拷贝构造(虽然用的'=',但是S4刚刚出现)
s3 = s2;//拷贝赋值(s3已经出现)

无指针的类,不需要写拷贝构造和拷贝赋值。类内带指针,一定要写拷贝构造和拷贝赋值,不能用编译器自动生成的。

构造函数

inline
String::String(const char* cstr = 0)
{
  if(cstr){
    m_data = new char[strlen(cstr) + 1];
    strcpt(m_data, cstr);
  }else{
    m_data = new char[1];
    *m_data = '\0';
  }
}

析构函数

inline
String::~String(){
    delete[] m_data;//array delete配合 array new
}

big three
class with pointer members 必须有 copy ctor 和 copy operator=。如果没有使用,则极易造成内存泄露,且两个类中的指针指向同一块内存,改变A,则B也被改变。

copy ctor
inline
构造函数,接受参数类型为本身,则为拷贝构造函数。
深拷贝:首先创造足够的空间,然后把内容拷贝到新的对象中。
浅拷贝:则会造成两个‘人’在‘看’同一个东西。

copy assignment operator 拷贝赋值函数
右边的对象拷贝的左边,左右两边原本都有内容

8.堆,栈与内存管理

8.1.Stack和Heap

Stack

是存在于某作用于(scope)的一块内存空间(memory space)。调用函数时,函数本身即会形成一个stack用来放置它所接收的参数,以及返回地址,以及local object。

Heap

是由操作系统提供的一块global内存空间,程序可以动态分配从其中获得若干区块,new出来的,必须手动delete掉。

stack objects的生命周期

stack object,即为local object,又称为 auto object,生命在作用域结束之后就结束了。对象的析构函数会被调用。

static local objects的生命周期

static object,其生命在作用域结束后仍然存在,直到整个程序结束。

global objects的生命周期

任何写在大括号之外的对象,其生命在main函数之前就存在,在程序结束之后才结束,作用域是“整个程序”。

heap objects的生命周期

new得到的对象,在使用完毕之后要delete掉。如果没有delete,则会造成内存泄露。

8.2 new delete

new:先分配memory,再调用ctor

Complex *pc = new Complex(1, 2);

编译器把new分解为三个动作:

delete:先调用dtor,在释放memory

String *ps = new String("Hello");
...
delete ps;

编译器把delete分解为两个动作:

共计两次delete。

8.3 动态分配所得的内存块(memory block)

1.动态分配所得的对象

1.Complex *pc = new Complex(1, 2);

在debug模式下,class的前面有32字节,后面有4字节,前后cooky各4字节,cooky为0x41,共计:

8+(32+4)+(4*2)=52 -> 64

在release模式下,class本身8个字节,前后cooky各4个字节,cooky为0x11,共计:

8+(4*2)=16 -> 16

上下cooky的作用,记录整块给你的大小。采用16进制,如果是0,则代表系统回收,如果是1,则代表系统给出。在vs的编译器下,给的内存的大小为16的倍数,所以cooky在16进制时最后一位一直为0,所以可以用来标记内存的方向。

2.String *ps = new String("Hello");

在debug模式下,cooky为0x31,共计:

4+(32+4)+(4*2)=48 -> 48

在release模式下,cooky为0x11,共计:

4+(4*2)=12 -> 16

2.动态分配所得的 array

array new 要搭配 array delete,不然会出错。

1.Complex *p = new Complex[3];

在debug模式下,cooky为0x51,共计:

(8*3)+(32+4)+(4*2)+4=72 -> 80

在release模式下,cooky为0x31,共计:

(8*3)+(4*2)+4=36 -> 48

2.String *p = new String[3];

在debug模式下,cooky为0x41,共计:

(4*3)+(32+4)+(4*2)+4=60 -> 64

在release模式下,cooky为0x31,共计:

(4*3)+(4*2)+4=24 -> 32

3.array new 一定要搭配 array delete

String *p = new String[3];
...
delete[] p; // 调用3次dtor
memory 解释
21h cooky记录内存大小
3 数组的大小
String[0] 调用dtor
String[1] 调用dtor
String[2] 调用dtor
000000000(pad) 填充内存
21h cooky记录内存大小
String *p = new String[3];
...
delete p; // 调用1次dtor
memory 解释
21h cooky记录内存大小
3 数组的大小
String[0] 调用dtor
String[1] 未调用dtor
String[2] 未调用dtor
000000000(pad) 填充内存
21h cooky记录内存大小

对比发现,整块的内存并没有发生内存泄露,因为整块内存的大小记录在cooky当中。如果没有写array delete而写的是delete,编译器不知道下面有几个对象,因此只有第一个也就是String[0]调用了dtor,其余的对象并没有调用dtor。当调用玩dtor之后,再释放掉整块的内存。由此可以发现,如果此时的例子是Complex类的话,那么由于类内部没有指针,所以即使用array new,但没用array delete,也不会产生内存泄露。

但是在写代码时,我们应养成好的编码习惯,array new 一定要搭配 array delete。

9.复习String类的实现

10.扩展补充:类模板,函数模板及其他

1.static

Class complex{
public:
    double real() const{
    return this->re;
    }
private:
    double re;
    double im;
};
complex c1, c2, c3;
cout << c1.real();
cout << c2.real();
complex c1, c2 ,c3;
cout << complex::real(&c1);
cout << complex::real(&c2);

同一个函数real(),之所以能处理不同对象的数据,靠的就是this point。

static data members 在内存的单独位置,有且只有一份。

static member functions
同样在内存的单独位置,函数本身也仅仅只有一份。但是跟一般的成员函数有个区别,它没有this point。它只能去处理静态的数据。

静态的变量,在类的内部只是生命,需要在类外部定义。类型 类名称::变量名(初始化操作);

调用静态函数的方法有两种:

单例模式,把ctors放在private区域。

class A{
public:
    static A& getInstance(){ return a; }
    setup()
private:
    A();
    A(const A& rhs);
    static A a;
    ...
};
A::getInstance().setup();

外界想要使用a,只能用过:getInstance()获得。

meyers Singleton:

class A{
public:
    static A& getInstance();
    setup()
private:
    A();
    A(const A& rhs);
    ...
};

A& A::getInstance(){
    static A a;
    return a;
}

当外界不需要使用这个类时,a不会被创建,只有当外界需要使用这个类,调用了getInstance()函数,a才会被创建。

2.关于cout

查看标准库ostream代码,重载了很多的operator<<

ostream

3.class template,类模板

在类的前面加上:

template<typename T>
class complex{
...
};

用T吧具体的类代替,当实际使用时,根据实际的需要,生成具体的类代码。

{
    complex<double> c1(2.5, 1.5); //用double代替T生成一份类的代码
    complex<double> c2(2, 6); //用int代替T生成一份类的代码
    
}

4.function template,函数模板

template<class T>
inline
const T& min(const T& a, const T& b){
    retuen a < b ? a : b;
}

所有比较大小都是这么操作,因此可以使用函数模板。实际比较时如何去比较,则依赖于需要比较大小的类。类似于这种函数,称之为算法。

{
    complex c1(1, 2), c2(3, 4), c3;
    c3 = min(c1, c2);//当调用min()函数时,编译器会进行实参推导(argument deduction),不必再使用的时候指定类型。
}

5.namespace

避免全局变量,函数以及类的同名,则需要namespace,如果每个人自己顶一个namespace,则不会造成冲突。

6.更多的细节,仍需努力

仍需努力的部分
上一篇 下一篇

猜你喜欢

热点阅读