C++笔记二(Boolan网——面向对象编程上)

七 三大函数(Big Three)

上文提到class有两种经典分类:
1.class without pointer members(complex)
2.class with pointer members(string)
根据string代码解析:

class String
{
    public:
        String(const char* cstr =0);
        String(const String& str);              //拷贝构造
        String& operator =(const String& str);  //拷贝赋值
        ~String():                              //析构函数
        char* get_c_str const () {return m_data};
    private:
        char* m_data;
};

m_data指向一串字符,动态分配内存空间来存放字符,而不是在类里面放数组,这样不易确定所要放数组的大小。
三个特殊的函数(Big Three):拷贝构造函数、拷贝赋值函数、析构函数。

7.1 析构函数

构造函数和析构函数:

inline   //构造函数
String::String(const char* cstr = 0)
{
    if (cstr)
    {
        m_data = new char[strlen(cstr) + 1];
        strcpy(m_data,cstr);
    }
    else //未指定值
    {
        m_data = new char[1];
        *m_data = '\0';
    }
}
inline  //析构函数
String::~String()
{
    delete [] m_data;
}

Complex类不需要清理,String是动态分配内存,析构函数用来清理这块内存,否则将会造成内存泄漏。

7.2 拷贝构造

带有指针的类必须要自己写拷贝构造函数和拷贝赋值函数,原因如下:
如果使用编译器给的默认拷贝构造函数,只是将a的地址拷贝到b中去,并没有拷贝a的m_data指向的那个内容,使得a的m_data和b的m_data指向同一个地方,b的m_data原本指向的“World”就成了一个孤立的内容,这样会造成内存泄漏。而且因为a和b都指向一块内存,更改a就会影响b,也是一个隐患,这个被称为浅拷贝。

所以要使用深拷贝的方法,即自己写一个拷贝构造函数。

inline
String::String(const String& str)
{
    m_data = new char[ strlen(str.m_data + 1) ];
    strcpy(m_data,str.m_data);
} //直接取另一个object的private data(兄弟之间互为friend)

{
    String s1("Hello");
    String s2(s1);    
 // String s3 = s1; 
}

7.3 拷贝赋值

inline String& String::operator = (const String& str)
{
    if (this == &str)      //检测自我赋值 
        return *this;

    delete[] m_data;
    m_data = new char[ strlen(str.m_data + 1)];
    strcpy(m_data,str.m_data);
    return *this;
}
{
    String s1("hello");
    String s2(s1);
    String s2=s1;         //拷贝赋值使用
}

自我赋值检测是必要的,如果没有自我赋值检测,delete[] m_data这一步骤会把自己的内容删掉,产生不确定行为。

八 堆,栈与内存管理

8.1 output函数

#include<iostream>
ostream& operator<<(ostream& os,const String& str)
{
    os << str.get_c_str();
    return os;
}
{
    String s1("hello");
    cout << s1; 

不能写成成员函数,只能写成全局函数,这样cout才会在左边,符合使用习惯。

8.2 stack(栈)和heap(堆)

(1)Stack:是存在于某作用域的一块内存空间(memory space)。例如当你调用函数,函数本身即会形成一个stack用来放置他所接受的参数,以及返回地址。在函数本体(function body)内声明的任何变量,其所使用的内存块都取自上述stack。
(2)Heap:或谓system heap,是指向操作系统提供的一块global内存空间,程序可动态分配(dynamic allocated)从某中获得若干块区(blocks)。

class Complex{...};
...
Complex c3(1,2);
{
     Complex c1(1,2);
     static Complex c2(1,2);
     Complex* p = new Complex(3);
     delete p;
}

上述代码中:
(1).c1所占用的空间来自stack,它叫做stack object,其生命在作用域(scope)结束之际结束,这种作用域内的object,又称为auto object,因为它会被自动清理;
(2).c2就是所谓static object,其生命再作用域(scope)结束之后仍然存在,直到整个程序结束;
(3).c3就是所谓global object,其生命在整个程序结束之后才结束。也可以把它视为一种static object,其作用于是整个程序。
(4).Complex(3)是个临时对象,其所占用的空间乃是new以heap动态分配而得,并由p指向。p指向的是heap object,其生命在它被delete之际结束,如果没有delete,将会出现内存泄漏,因为当作用域结束,p所指的heap object仍然存在,但指针p的生命却结束了,作用域之外再也看不到p(也就没机会delete p)。

8.3 new和delete

1.Complex的new和delete编译分析:
new:先分配memory,再调用ctor。new调用的C的malloc函数。


1.png

delete:先调用dtor,再释放memory。delete调用C的free函数。


2.png

2.String的new和delete编译分析:
new:先分配memory,再调用ctor。
3.png

delete:先调用dtor,再释放memory。


4.png

动态分配所得内存块(in vc):
5.png

红色部分被称为cookie(小甜饼干)用来记所分配内存块的大小,大小是16bit的倍数,末位表示输入(0)和输出(1);灰色部分表示debug代码区,不需要调试可以不要;浅绿色表示对象所存在的内存区域;深绿色是为了使分配的内存为16的倍数所填补的,称为pad。

动态分配所得的array:


6.png

array new一定要搭配array delete,否则也会造成内存泄漏:


7.png

九 String类完整代码


#ifndef __MYSTRING__
#define __MYSTRING__

class String
{
public:                                 
   String(const char* cstr=0);                     
   String(const String& str);                    
   String& operator=(const String& str);         
   ~String();                                    
   char* get_c_str() const { return m_data; }
private:
   char* m_data;
};

#include <cstring>

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

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

inline
String& String::operator=(const String& str)
{
   if (this == &str)
      return *this;

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

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

#include <iostream>
using namespace std;

ostream& operator<<(ostream& os, const String& str)
{
   os << str.get_c_str();
   return os;
}

#endif

十 类模板、函数模板及其他补充

10.1 static

8.png

1.非静态成员变量中,使用者如图创建了三个对象,在内存模型中,每个对象都有一份成员变量,通过this指针(每个对象的地址)来区别是哪个对象调用对应的成员变量。同样的一份成员函数处理多个对象的需求也是通过this指针实现的。
2.在成员变量或者成员函数前面加上关键字static,那么就变成静态的成员变量和静态的成员函数,脱离了对象,只有一份。应用比如设计一种银行账户体系,用户的账号是每一个对象,银行的利率和对象无关,这时把利率设计成静态的成员变量。静态成员函数没有this pointer,不能像一般的成员函数一样去访问、处理对象里面的数据,静态函数只能处理静态的数据。
静态的数据在class的外头必须要定义,初值可以给也可以不给,如下图黄色部分:


9.png
int main()
{
    Account::set_rate(5.0);  //通过class name调用
    Account a;
    a.set_rate(7.0);         //通过object调用
}

调用static函数的方式有两种:
(1)通过object调用;
(2)通过class name调用。

10.2 singleton补充

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

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

如上述代码所示,singleton通过static实现,外界不能创建A的对象,只能通过A::getInstance().setup这样的方式调用函数。当外界不需要调用这个函数,a仍然存在,造成浪费,更好的设计如下:

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

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

10.3 cout

cout能接受不同类型的参数是因为大量作了<<的重载,cout是一种ostream。


10.png

10.4 类模板和函数模板

类模板示例:


11.png

函数模板示例:


12.png

关键字class和typename是相通的。函数模板用的时候,编译器会对它进行实参推导,不用加尖括号。函数模板里面<的重载由设计stone类的人去设计,这是合理的,编译器是不知道如何比大小的。

10.5 namespace

所有的东西被包装一个命名空间里面,防止不同公司相同函数名调用的混乱。
有三种打开这个命名空间的方式:


13.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容