Boolan/C++面向对象高级编程 part2

C++面向对象高级编程 part2

@(boolan C++)[C++]
2017-10-22 14:17:56 / helingchao


概述

本章节内容主要讲述如何设计class with pointer,以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 

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 
using namespace std;

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

#endif


big three

所谓big three即:

  1. 拷贝构造
  2. 拷贝赋值
  3. 析构

1. 编译器提供的默认函数

声明一个空类

声明一个空类,如下:

class Empty {
}

C++会为上面定义的Empty类,提供默认的成员。效果等同于如下定义:

class Empty {
public:
Empty() {...};
~Empty(){...};
Empty(const Empty& rhs){...};
Empty& operatpr = (const Empty& rhs) {...};
}


C++提供的默认函数

  • default 构造函数
  • copy构造函数
  • 析构函数
  • copy assignment函数

原则 01: C++默认提供的函数仅在被调用时,编译器才会创建它们。
注意:这四个函数都是public和inline的。


默认函数的行为都是什么

default构造和析构函数

default 构造函数和析构函数主要是给编译器一个地方放置“藏身幕后”的代码。

  • default构造函数 :调用base classesnon-static 成员变量的构造函数。
  • 析构函数 :调用base classesnon-static 成员变量的析构函数。

注意: non-static成员的说法,static 类成员不在default构造函数的初始化范围内。

copy构造函数和copy assignment

单纯的将对象的每一个non-static成员拷贝到目标对象。

用户需要重新定义copy构造函数和copy assignment的场景

类中包含以下类型成员

  • 引用类型
  • 指针类型
  • const类型

2. 字符串设计

Boolan/C++面向对象高级编程 part2_第1张图片
1509260994792.png
  1. 用指针保存存储数据的地址,不用数组。好处在于灵活的存储,无需考虑设计多大的数组。

实际是一种动态存储思想与静态存储思想之间的决策。

3. ctor & dtor

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] = '\0';
    }
}

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

字符串的两种形式:

  1. 串+‘\0’
  2. 长度字段+串

4. copy ctor & copy op =

Boolan/C++面向对象高级编程 part2_第2张图片
1509261365517.png

浅拷贝 vs. 深拷贝

在copy对象时仅copy指针成员属于浅copy, 创建新的内存并将指针的内容copy到新内存是深copy。

在class with pointer 默认的copy ctor & copy op = 使用的是浅拷贝。

浅拷贝造成的问题:

浅拷贝会造成 alias(别名) 和 memory leak

alias 与 野指针/悬空指针

单一个内存单元被多个对象所只向时,可以看作是该内存单元存在了别名。

// p_a 与p_b 构成了alias 
int *p_a = new int();
int *p_b = p_a;

alias引入的问题:
内存单元被释放时,其他地点的别名可能不知道该内存以被释放,继而继续使用该内存,产生不确定的行为(undefined behavior)

Boolan/C++面向对象高级编程 part2_第3张图片
1509262248771.png

什么是悬空指针(dangling pointer)?

If a pointer still references the original memory after it has been freed, it is called a dangling pointer.

悬空指针是指针最初指向的内存已经被释放了的一种指针。

什么是野指针(wild pointer)?

A pointer in c which has not been initialized is known as wild pointer.

野指针(wild pointer)就是没有被初始化过的指针.

5. 拷贝赋值/copy assignment operator

string& string::operator = (const string& str_r) {
    if (this == &str_r) {  // 自我检测
        return *this;
    }

    delete[] m_data;
    m_data = new char[strlen(str_r.m_data) + 1];
    strcpy(m_data, str_r.m_data);

    return *this;
}

注意⚠️:
拷贝赋值中的自我检测

copy op=的动作:

  1. 清空已有的数据
  2. 创建新内存
  3. 拷贝

拷贝构造与拷贝赋值的差别

  1. 拷贝构造是创建新对象,拷贝已有对象到新对象
  2. 拷贝赋值时,两个对象都已经被创建。

基于上述两点拷贝构造无需检测自我赋值,但拷贝赋值需要。


内存管理

1. stack vs. heap

Boolan/C++面向对象高级编程 part2_第4张图片
1509263493997.png

stack, 是存在于某个作用域的一块内存空间。 例如函数被调用时,函数本身就会生成一个stack。

heap,是指由操作系统提供的一块gloabl内存空间,可由程序员动态创建。

2. static object & global object

两者在生命周期都是在main结束之后,程序结束之后才结束。

3. new: 先创建内存,后调用ctor

Boolan/C++面向对象高级编程 part2_第5张图片
1509262367694.png

new的三个步骤

// 原始语句
complex* pc = new complex(1,2);
// 编译器内部转换后的遇见
void* mem = opreator new(sizeof(complex));  // 1. 分配内存
pc = static_cast(mem);  // 2. 类型转化
pc->complex::complex(1,2);  // 3. 调用构造函数

成员函数的调用过程

哪个对象调用类成员函数,(编译器)默认将该对象的指针传给被调用成员函数this。

pc->complex::complex(1,2);

complex::complex(pc,1,2);

3. delete:先调用dtor, 后释放内存

Boolan/C++面向对象高级编程 part2_第6张图片
1509262410676.png

4. array new 一定要搭配 array delete

Boolan/C++面向对象高级编程 part2_第7张图片
1509262539128.png

扩展补充:类模版..

1. C++对象内存模型

  1. non-static 数据成员
  2. static 数据成员
  3. non-static 成员函数
  4. static 成员函数
Boolan/C++面向对象高级编程 part2_第8张图片
1509263831824.png
  1. static 数据成员/static成员函数/none -static成员函数 全局只有一份。
  2. none-static数据成员每个对象各有一份

this指针

哪个对象调用成员函数,哪个对象的指针就会作为参数传递给成员函数的this指针

complex c1;
c1.real();
complex::real(&c1);
  1. none-static 成员函数通过this指针将处理不同对象的non-static数据。
  2. this pointer如何传递:non-static 成员函数是全局唯一的,哪个对象调用ns成员函数就将哪个对象的地址作为this指针传递给ns成员函数
1509262669674.png

static 成员

static成员脱离于对象。成员函数也是脱离于对象。

  1. static 成员函数没有this pointer
  2. st 成员函数只能处理 st数据
  3. st成员函数没有this指针所以不能处理非st数据

静态数据要在classbody外定义,定义时不要添加static声明。

class acount{
    static int a;
}

int acount::a;  // 类st成员定义
  1. st 成员数据要在class body 外“定义”。这里是定义,因为要分配内存。
  2. static 成员函数的两种调用方式:object调用,classname 调用。

设计static成员的原则

如果数据是在对象间通用的,则将该数据设计为类的static成员。

static 版本的singleton

class singleton {
public:
    static singleton& get_instance() {
        static singleton a;
        return a;
    }
private:
    singleton() {};
    
}

cout

Boolan/C++面向对象高级编程 part2_第9张图片
1509263864546.png

class template

Boolan/C++面向对象高级编程 part2_第10张图片
1509263880945.png

function template

Boolan/C++面向对象高级编程 part2_第11张图片
1509263907947.png

argument deduction (实参推导):

  1. argument deduction (实参推导):不同于class template , function template 在使用时不用指定具体类型

名称空间

你可能感兴趣的:(Boolan/C++面向对象高级编程 part2)