第二周:C++实现一个带指针成员变量的类【Boolean】

1.Big Three

class String // Big Three: 拷贝构造、拷贝复制、析构
{

    String (const String &str);
    String &operator=(const String &str);
    ~String();

};

(1).构造函数

inline
String::String(const char*cstr = 0)
{
    if (cstr) {

        m_data = new char[strlen(cstr)+1]; // C++ 使用'\0'表示字符存储的结束 ,strlen()不计算结束符'\0',所以要+1

        strcpy(m_data, cstr);
    }
    else {
        m_data = new char[1];   //[1]是为了存储'\0'结束符,析构时方便
        *m_data = '\0';
    }
}

(2).Big Three 之拷贝构造函数

String s1(“hello”);

String s2(s1);
….. ‖ 完全相同,都是调用的拷贝构造
String s2 = s1;

inline
String::String(const String &str)
{
    m_data = new char[strlen(cstr)+1]; 
    strcpy(m_data, cstr);
}

(3).Big Three 之拷贝复制函数

  1. 自我检查,如果是自己,直接返回
  2. 删除当前的内容
  3. 开辟新内存空间,获取右值str对象内容
  4. 返回自身引用
inline
String& String::operator=(const String& str)
{
    if(this == &str)
    { return *this;}

    delete[] m_data;
    m_data = new char[strlen(str)+1];
    strcpy(m_data, str.m_data);
    return *this;
}

(4).Big Three 之析构函数

inline String::~String()
{   
    delete [] m_data;   
}

如果类里面有指针,动态分配内存,一定要再析构函数中释放内存,避免内存泄漏


2.创建对象

Stack,是存在于某个作用域(scope)的一块儿内存空间(memory
space)。例如当你调用构造函数,函数本身即会形成一个stack用来防止它所接收的参数,以及返回地址。

在函数体内(function body)内声明的任何变量,其所使用的内存块都取自上述stack。

Heap 所谓system heap,是指由操作系统提供的一块global内存空间,程序可动态分配(dynami
callocated)从中获得若干区块(blocks)。

{
Complex c1(1,2);              //c1所占用的空间来自stack

Complex* p = new Complex(3); // Complex(3)是个临时对象,其所占用的空间仍是以new自heap动态分配而得,并由p指向。
}

stack object的生命期

class Complex{...};
...
{
    Complexc1(1,2);
}

c1是所谓的stack object,其生命在作用域(scope)结束之际结束。这种object,称为local object 或称 auto objcet,因为他会被自动清理。

class Complex{...};
...
{
    static Complexc2(1,2);
}

c2是所谓的static object,其生命再作用于(scope)结束之后仍然存在,直到整个程序结束。

heap object的生命期

class Complex{...};
...
{
    Complex* p = new Complex;
    ...
    delete p;
}

p 所指的是 heap object,其生命周期在它被deleted之际结束。

class Complex{ ... };
...
{
    Complex* p = new Complex;
}

以上代码会出现内存泄漏(memory leak),因为当作用域结束,p所指的heap object仍然存在,但指针p的生命却结束了,作用域之外再也看不到p(也就没机会delete p)

global objects的生命周期

class Complex{...}
...
Complex c3(12);

int Main()
{
...
}

c3是 global objects,其生命在整个程序结束之后才结束。可以把它视为一种 static object,其作用域是【整个程序】。


【new的原理:先分配内存,在调用构造函数】

当new一个对象的时候,实际上是 先分配内存,再调用类的构造函数

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

void mem = operator new (sizeof(Complex));  // 分配内存:内部使用malloc分配内存
pc = static_cast<Complex*>(mem);            // 转型
pc->Complex::Complex(1,2);                  // 构造函数,相当于伪码: Complex::Complex(pc, 1, 2)

【delete:先调用析构函数,再释放内存】

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

编译器转化为:

String::~String(ps);
operator delete(ps)     // 内部调用 free(ps),使用free释放内存

3.扩展

singleton模式

class A
{
public:
    static A &getInstance()
    {
        static A a;
        return a;
    }
    static int num;
private:
    A() { }
};

int A::num = 10;    //类的静态成员初始化方法

int main(int argc, char *argv[])
{
    A::getInstance();       //通过类名来调用
    return 0;
}

类模板

template <typename T>
class A
{
public:
    A(T a)
    :m_a(a)
    {}
    T m_a;
};

//调用
A<int>  a;  //类模板调用需要指明模板类型

函数模板

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

//调用
class A
{
...
};

A a,b;
min(a,b)    ===> 函数模板调用不需要指明参数对象,编译器会对实参进行推导,然后确定类型

namespace 命名空间

#include <iostream>

using namespace std;    //打开标准库 的命名空间

//自定义命名空间
namespace mySpace
{
    int a;
}

int main(int argc, char*argv[])
{
    mySpace::a = 10;
    return 0;
}

参考链接:使用namespace的三种方式

operator type() const;

#include <iostream>  
#include <string>  
#include <string.h>  
#include <stdlib.h>  
#include <stdio.h>  

using namespace std;  

class Total  
{  
public:  
    Total(float sum,float discount)  
    {  
        sum_ = sum;  
        discount_ = discount;  
    }  
    ~Total(){}  

    operator std::string()  
    {  
        cout<<"operator std::string() "<<endl;
        char str[128];  
        sprintf(str,"%f",sum_* discount_);  
        return std::string(str);  
    }  

    operator int()  
    {  
        cout<<"operator int()"<<endl;
        return sum_* discount_;  
    } 

    operator float()  
    {  
        cout<<"operator float()"<<endl;
        return sum_* discount_;  
    }  

    float operator()()  
    {  
        cout<<"float operator()()   "<<endl;
        return sum_* discount_;  
    }  
    float sum_;  
    float discount_;  
};  

ostream& operator<< (ostream &out, const Total & ths)
{
    cout << "--------------------ostream& << (ostream &out, const Total & ths)"<<endl;
    return out;
}

int main(int argc, char const *argv[])  
{  
    Total to(89, 0.8);  
    cout << to << endl;  
    cout << to() << endl;  
    cout << (std::string)to << endl;  
    cout << (float)to << endl;  
    cout << (int)to << endl; 
    return 0;  
}  

输出
--------------------ostream& << (ostream &out, const Total & ths)

float operator()()
71.2
operator std::string()
71.200001
operator float()
71.2
operator int()
71

需要注意的是如果没有重载<<,并且Total里面有operator float(),operator int()编译器就会报错,有二义性,因为不确定cout << to << endl 是把Total转成int,还是float,然后进行输出,如果只有一个,就不会有问题

explicit complex():initialization list {}

关键字explicit,可以阻止不应该允许的经过转换构造函数进行的隐式转换的发生。声明为explicit的构造函数不能在隐式转换中使用。

class Test1
{
public:
    Test1(int n) { num = n; } //普通构造函数
private:
    int num;
};

class Test2
{
public:
    explicit Test2(int n) { num = n; } //explicit(显式)构造函数
private:
    int num;
};
int main(int argc, char *argv[])
{
    Test1 t1 = 12;  //隐式调用其构造函数, 成功
    //Test2 t2 = 12;    //编译错误,不能隐式调用其构造函数
    Test2 t3(12);   //显示调用成功
    return 0;
}

function-like object

参考链接:std::function from cppreference.com

以下代码需要编译器C++11支持

#include <functional>
#include <iostream>

struct Foo {
    Foo(int num) : num_(num) {}
    void print_add(int i) const { std::cout << num_+i << '\n'; }
    int num_;
};

void print_num(int i)
{
    std::cout << i << '\n';
}

struct PrintNum {
    void operator()(int i) const
    {
        std::cout << i << '\n';
    }
};

int main()
{
    // store a free function
    std::function<void(int)> f_display = print_num;
    f_display(-9);

    // store a lambda
    std::function<void()> f_display_42 = []() { print_num(42); };
    f_display_42();

    // store the result of a call to std::bind
    std::function<void()> f_display_31337 = std::bind(print_num, 31337);
    f_display_31337();

    // store a call to a member function
    std::function<void(const Foo&, int)> f_add_display = &Foo::print_add;
    const Foo foo(314159);
    f_add_display(foo, 1);

    // store a call to a data member accessor
    std::function<int(Foo const&)> f_num = &Foo::num_;
    std::cout << "num_: " << f_num(foo) << '\n';

    // store a call to a member function and object
    using std::placeholders::_1;
    std::function<void(int)> f_add_display2 = std::bind( &Foo::print_add, foo, _1 );
    f_add_display2(2);

    // store a call to a member function and object ptr
    std::function<void(int)> f_add_display3 = std::bind( &Foo::print_add, &foo, _1 );
    f_add_display3(3);

    // store a call to a function object
    std::function<void(int)> f_display_obj = PrintNum();
    f_display_obj(18);
}

//输出
-9
42
31337
314160
num_: 314159
314161
314162
18

4. 作业知识点

虚析构

为了当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用。

class Shape
{
public:
    Shape(int no = 0)
        :m_no(no)
    {   }
    virtual ~Shape(){}
    virtual int getArea()=0;
    void setNo(int no)  { m_no = no; }
    int getNo() {return m_no;}
protected:
    int m_no;
};

class Rect:public Shape
{
public:
    Rect():m_p(0)
    {}
    ~Rect(){}
private
    char *m_p;
}

int main(int argc, char *argv[])
{
    Shape *p = new Rect;

    delete p;       
    return 0;
}

上例代码,如果Shape的析构函数不是virtual的话,delete的时候就不会调用Rect的析构函数,可能会造成Rect::m_p的内存泄漏

还可以参考这个例子:析构函数什么情况下要定义为虚函数?

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值