CPP 学习 - 类和对象

一、类中的默认函数 + this 指针

(一)默认函数 + this 指针

  1. 构造函数

  2. 析构函数

  3. 拷贝构造函数

  4. 赋值运算符函数

    #include <string.h>
    
    class Point
    {
    public:
        Point(int x, int y);					// 构造函数
        ~Point();								// 析构函数
        Point(const Point &rhs);				// 拷贝构造函数
        Point &operator = (const Point &rhs);	// 赋值运算符函数
        
    private:
        char *_contant;
        int _ix;
        int _iy;
    }
    

1. 构造函数

tips

  1. 初始化列表的赋值顺序应当和成员变量定义顺序相同
  2. 初始化与赋值不同,具体体现在于三种特殊数据成员:常量成员、引用成员、类对象成员

实现代码
Point::Point(char *contant, int x = 0, int y = 0)
    : _contant(new char[strlen(contant + 1)]())
	, _ix(x)
    , _iy(y)
{ 
	strcpy(_contant, contant);
}


2. 析构函数

tips

  1. 一个类只有唯一一个析构函数,析构函数不能重载

  2. 在堆中创建变量时,不会自动执行析构函数

    指针仍指向堆空间的类成员,所以类未被及时销毁,所以需要手动执行析构函数

  3. 对象的销毁与析构函数不等价

    堆对象,对象销毁时,除了执行析构函数,还需要执行 operator delete(void *)


析构函数调用时机
  1. 对于全局定义的对象,每当程序开始运行,在主函数main接受程序控制权之前,就调用构造函数创建全局对象,整个程序结束时自动调用全局对象的析构函数。
  2. 对于局部定义的对象,每当程序流程到达该对象的定义处就调用构造函数,在程序离开局部对象的作用域时调用对象的析构函数。
  3. 对于关键字static定义的静态局部变量,当程序流程第一次到达该对象定义处调用构造函数,在整个程序结束时调用析构函数。
  4. 对于用new运算符创建的对象,每当创建该对象时调用构造函数,当用delete删除该对象时调用析构函数。

实现代码
Point::~Point()
{
    delete _contant;
    _contant = nullptr;
}


3. 拷贝构造函数

拷贝函数调用时机
  1. 当用一个已经存在的对象初始化另一个新对象时,会调用拷贝构造函数。

    Point pt2 = pt1

  2. 当实参和形参都是对象,进行实参与形参的结合时,会调用拷贝构造函数。

    void func(Point pt) { // 结构体 }

  3. 当函数的返回值是对象,函数调用完成返回时,会调用拷贝构造函数。

    Point func() { // 结构体 }

    编译器会自动优化该返回值,若要实现则通过以下命令进行编译

    g++ [filename].cc -fno-elide-constructors

tips:当类类型中有数组时,需注意浅拷贝和深拷贝


实现代码
#include <string.h>

Point::Point(const Point &rhs)
    : _contant(new char[strlen(rhs.contant) + 1]())
    , _ix(rhs._ix)
    , _iy(rhs._iy)
{
	strcpy(_contant, rhs.contant);	// 深拷贝
}

const Point &rhs中需要注意的事项:

  1. &rhs 中的 &
    由于当创建临时变量时,会调用拷贝构造函数,若不为引用,即直接为 rhs,则在创建实参时,再次执行拷贝构造函数,反复如此,从而导致栈溢出
  2. const
    必须是 const 类型,才能指向右值


4. 赋值运算符函数

步骤:

  1. 自复制
  2. 释放左操作数
  3. 深拷贝(包含空间的申请)
  4. 返回 *this

实现代码
Point &Point::operator=(const Point &rhs)
{
    if(this == &rhs)	// 返回本值时,由于先 delete 成员函数,从而会导致值丢失
        return *this;
    
    delete[] _contant;
    _contant = nullptr;
    
    _contant = new char[strlen(rhs._contant) + 1]();
    strcpy(_contant, rhs._contant);
    
    return *this;
}

tips

  1. 如果去掉返回类型的 &,那当返回时,返回的为类本身,从而调用一次拷贝构造函数,造成浪费

  2. 如果不返回 Point &,比如返回类型为 void,则当连等时,会异常

    Point pt1, pt2, pt3;
    pt3 = pt2 = pt1;	// 若不为 Point &,此处会报错
    

调用方法
Point pt1, pt2;
pt2 = pt1;
pt2.operator=(pt1);	// 两者等价


5. this 指针

在类中定义的非静态成员函数中都有一个隐含的this指针,它代表的就是当前对象本身,它作为成员函数的第一个参数,由编译器自动补全。

this 指针本身不能修改,是顶层 const / 指针常量 / *const

案例代码
void Point::print(Point * const this) {
    // 结构体
}


还有

移动构造函数

Point(const Point&& rhs); // && 右值引用

移动拷贝函数

Point& operator=(Point&& rhs);

有缘再写!




(二)几种模板 - ?不确定

= delete

  1. 禁止使用编译器默认生成的函数

  2. delete 关键字可用于任何函数,不仅仅局限于类的成员函数

  3. 模板特化:在模板特例化中,可以用delete来过滤一些特定的形参类型



= default

​ 相当于无参的构造函数

参考博客:https://blog.csdn.net/lmb1612977696/article/details/80035487

https://docs.microsoft.com/en-us/cpp/cpp/explicitly-defaulted-and-deleted-functions?view=vs-2019#explicitly-defaulted-functions



= 0

虚函数 ==> 纯虚函数,详情见 - 继承和多态

有缘再写!

学完再改





(三)类中的特殊数据成员

  1. 常量成员
  2. 引用成员
  3. 类对象成员
  4. 静态成员

常量成员、引用成员、类对象成员

tips:必须在构造函数初始化列表中初始化

引用成员占据一个指针空间大小

类对象成员如果不显示初始化,会调用默认构造函数,若类对象的类类型无默认构造函数,则会报错



静态数据成员

tips

  1. 静态数据成员在编译时就被创建并初始化
  2. 不能在 [filename].h中定义,会导致多次定义 ——> 必须在 [filename].cc中定义
  3. 静态数据成员不占类的空间

留个坑:
静态成员变量,在类中的声明定义
static const std::string = “xxx”; // 异常!

有缘再改!





(四)类中的特殊成员函数

1. 静态成员函数

静态成员函数的特点
  1. 静态成员函数内部不能使用非静态的数据成员和非静态的成员函数

  2. 静态成员函数内部只能调用静态数据成员和静态的成员函数

    原因:无 this 指针

  3. 可以直接使用类名,来调用静态成员函数

tips:声明时需要加上 static,但是在实现时,不能加 static 修饰



2. const 成员函数

const 成员函数的特点
  1. const 成员函数的 this 指针类型为 const [classname] *const this
  2. 与非 const 成员函数可以进行重载,但 const 成员函数不能修改数据成员
  3. const 对象只能调用 const 成员函数,非 const 成员函数均可调用

定义方法
void point() const;		// 静态成员函数,通常优先写静态成员函数,再写普通成员函数
void point();			// 普通成员函数,非 const 对象优先调用普通成员函数




(五)对象的组织

  1. const 对象
  2. 指向对象的指针
  3. 对象数组
  4. 堆对象

对象数组

代码示例
Point pt1[3] = {Point(1, 2), Point(3, 4)}	// 会有三个对象创建

Point pt2[3] = {{1, 2}, {3, 4}}				// 等价

Point pt2[3] = {(1, 2), (3, 4)}				// 错误
/* 
 * (1, 2) 为逗号表达式,即执行表达式中最后的部分
 * pt2[0] = Point(2, 0);
 * pt2[1] = Point(4, 0);
*/




二、几种设计模式

1. 单例模式

实现代码

// Singleton.h
class Singleton
{
public:
	static Singleton *getInstance()
    {
        if(nullptr == _pInstance)
            _pInstance = new Singleton();
        
        return _pInstance;
    }
    
    static void destory()
    {
        if(_pInstance)
        {
            delete _pInstance;
            _pInstance = nullptr;
        }
    }
    
private:
    static Singleton *_pInstance;
    
    singleton() {}
    ~singleton() {}
}



// Singleton_test.cc 文件
Singleton *Singleton::_pInstance = nullptr;

int main()
{
    Singleton *ps = Singleton::getInstance();
    
    Singleton::destroy();
    
    return 0;
}


单例模式的自动释放

四种释放方法
  1. 友元的形式
  2. 内部类 + 静态数据成员形式
  3. atexit形式
  4. pthread_once形式

1. 以友元的形式
#include <iostream>

using std::cout;
using std::endl;

class AutoRelease;

class Singleton
{
    // 友元函数,实现自动释放
    friend AutoRelease;

public:
    static Singleton *getInstance()
    {
        if(nullptr == _pInstance)
            _pInstance = new Singleton();

        return _pInstance;
    }

    // 用友元函数替代 destroy()
    /* static void destroy() */
    /* { */
    /*     delete _pInstance; */
    /*     _pInstance = nullptr; */
    /* } */

private:
    Singleton()
    {
        cout << "Singleton()" << endl;
    }

    ~Singleton()
    {
        cout << "~Singleton()" << endl;
    }

    static Singleton *_pInstance;
};

// 饿汉模式和懒汉模式
// 懒汉模式 - 在类加载时,不创建实例,所以类加载速度快,但运行时获取对象的速度慢
// 饿汉模式 - 在类加载时完成初始化,所以类加载慢,但获取对象的速度快
// 在多线程环境,使用懒汉模式可能会导致单例模式的失效
/* Singleton *Singleton::_pInstance = nullptr; // 懒汉模式 */
Singleton *Singleton::_pInstance = getInstance();   // 饿汉模式

class AutoRelease
{
public:
    AutoRelease()
    {
        cout << "AutoRelease()" << endl;
    }

    ~AutoRelease()
    {
        cout << "~AutoRelease()" << endl;

        delete Singleton::_pInstance;
        Singleton::_pInstance = nullptr;
    }
};

int main()
{
    // 创建 Singleton 的单例对象 _pInstance
    Singleton::getInstance();

    // 生成 AutoRelease 对象,其中该友元类在生命周期结束后会自动调用析构函数
    // 在 ~AutoRelease() 中,会销毁单例对象 _pInstance
    AutoRelease ar; // 栈对象

    // 由于第一次 AutoRelease 的析构函数中已经删除单例对象 _pInstance
    // 本次的析构函数中不会再次调用 ~Singleton()
    AutoRelease ar2; // 栈对象

    return 0;
}

/***
 * 运行结果:
 * Singleton()
 * AutoRelease()
 * AutoRelease()
 * ~AutoRelease()
 * ~Singleton()
 * ~AutoRelease()
 */   

2. 内部类 + 静态数据成员形式
#include <iostream>

using std::cout;
using std::endl;

class Singleton
{
public:
    static Singleton *getInstance()
    {
        if(nullptr == _pInstance)
            _pInstance = new Singleton();

        return _pInstance;
    }

    /* static void destroy() */
    /* { */
    /*     delete _pInstance; */
    /*     _pInstance = nullptr; */
    /* } */

private:
    Singleton()
    {
        cout << "Singleton()" << endl;
    }

    ~Singleton()
    {
        cout << "~Singleton()" << endl;
    }

    // 内部私有成员
    class AutoRelease
    {
    public:
        AutoRelease()
        {
            cout << "AutoRelease()" << endl;
        }

        ~AutoRelease()
        {
            cout << "~AutoRelease()" << endl;
            delete _pInstance;
            _pInstance = nullptr;
        }
    };

    static Singleton *_pInstance;
    // 内部私有成员
    static AutoRelease _ar;
};

/* Singleton *Singleton::_pInstance = nullptr; // 懒汉模式 */
Singleton *Singleton::_pInstance = getInstance();   // 饿汉模式
// 初始化
Singleton::AutoRelease Singleton::_ar;

int main()
{
    Singleton::getInstance();

    return 0;
}

/***
 * 运算结果:
 * AutoRelease()
 * Singleton()
 * ~AutoRelease()
 * ~Singleton()
 */

3. atexit形式
#include <iostream>                                                               

#include <stdlib.h>

using std::cout;
using std::endl;

// atexit 介绍
// 使用方法:int atexit(void (*function)(void));
// 作用:调用该方法后,在程序正常结束后,调用该函数指针指向的函数
class Singleton
{
public:
    static Singleton *getInstance()
    {   
        if(nullptr == _pInstance)
        {
            _pInstance = new Singleton();
            atexit(destroy);
        }

        return _pInstance;
    }   

    static void destroy()
    {   
        delete _pInstance;
        _pInstance = nullptr;
    }   

private:
    Singleton()
    {   
        cout << "Singleton()" << endl;
    }   

    ~Singleton()
    {   
        cout << "~Singleton()" << endl;
    }   

    static Singleton *_pInstance;
};

/* Singleton *Singleton::_pInstance = nullptr; // 懒汉模式 */
Singleton *Singleton::_pInstance = getInstance();   // 饿汉模式

int main()
{
    Singleton::getInstance();

    return 0;
}

4. pthread_once形式 - Linux 环境
#include <iostream>

#include <pthread.h>
#include <stdlib.h>

using std::cout;
using std::endl;

// pthread_onec 介绍
// 使用方法:int pthread_once(pthread_once_t * once_-control
//                  void (*init_routine)(void));
// 作用:在首次执行 pthread_once 时,会执行 init 函数
//      只有首次运行该函数时,才会执行 init,由宏 PTHREAD_ONCE_INIT 来记录
//      所以,在多线程环境中,pthread_once 依旧可以用懒汉模式初始化变量
// pthread_once 是 POSIX - Linux 下的标准,所以只能在 Linux 系统中使用,跨平台性较差
// 执行时需要加载线程库:
//      g++ Singleton_pthread_once -lpthread
class Singleton
{
public:
    static Singleton *getInstance()
    {   
        pthread_once(&_once, init);

        return _pInstance;
    } 

    static void init()
    {
        _pInstance = new Singleton();
        atexit(destroy);
    }

    static void destroy()
    {   
        delete _pInstance;
        _pInstance = nullptr;
    }   

private:
    Singleton()
    {   
        cout << "Singleton()" << endl;
    }   

    ~Singleton()
    {   
        cout << "~Singleton()" << endl;
    }   

    static Singleton *_pInstance;
    static pthread_once_t _once;
};

Singleton *Singleton::_pInstance = nullptr; // 懒汉模式
pthread_once_t Singleton::_once = PTHREAD_ONCE_INIT; // 宏

int main()
{
    Singleton::getInstance();

    return 0;

}


2. PIMPL 模式 - 编译防火墙

PIMPLPrivate ImplementationPointer to Implementation):通过一个私有的成员指针,将指针所指向的类的内部实现数据进行隐藏。

PIMPL又称作“编译防火墙”,它的视线中就用到了嵌套类。

PIMPL设计模式的优点

  1. 提高编译速度
  2. 实现信息隐藏
  3. 减小编译依赖,可以用最小的代价平滑的升级库文件
  4. 接口与实现进行解耦
  5. 移动语义友好

案例代码

Line.h

#ifndef __LINE_H
#define __LINE_H

class Line
{
public:
    Line(int, int, int, int);
    ~Line();
    void printLine() const;

private:
    // 将 Line 的私有成员的实现封装在 class - LineImpl 中
    class LineImpl;
    LineImpl *_pimpl;
};

#endif // __LINE_H

Line.cc

#include <iostream>

#include "Line.h"

using std::cout;
using std::endl;

// 实现 Line 的私有成员的实现
class Line::LineImpl
{
public:
    // 构造函数
    LineImpl(int x1, int y1, int x2, int y2)
        : _pt1(x1, y1)
        , _pt2(x2, y2)
    {}
    void printLineImpl() const;

private:
    // 私有类
    class Point
    {
    public:
        Point(int x = 0, int y = 0)
            : _x(x), _y(y)
        {}
        void print() const;

    private:
        int _x;
        int _y;
    };

    Point _pt1;
    Point _pt2;
};

void Line::LineImpl::Point::print() const
{
    cout << "(" << _x
         << ", " << _y
         << ")";
}

void Line::LineImpl::printLineImpl() const
{
    _pt1.print();
    cout << " ----> ";
    _pt2.print();
    cout << endl;
}

Line::Line(int x1, int y1, int x2, int y2)
    : _pimpl(new LineImpl(x1, y1, x2, y2))
{}

Line::~Line()
{
    delete _pimpl;
    _pimpl = nullptr;
}

void Line::printLine() const
{
    _pimpl->printLineImpl();
}

Line_test.cc

#include "Line.h"

int main()
{
    Line the_line(1, 2, 3, 4);
    the_line.printLine();

    return 0;
}




三、new 与 delete 表达式

工作步骤

new 表达式工作步骤

  1. 调用名为 operator new 的标准库函数,分配足够大的原始的未类型化的内存,以保存指定类型的一个对象
  2. 运行该类型的一个构造函数初始化对象
  3. 返回指向新分配并构造的构造函数对象指针`

delete 表达式工作步骤

  1. 调用析构函数,回收对象中数据成员所申请的资源
  2. 调用名为operator delete的标准库函数释放该对象所用的内存

顺序不能调换operator delete会将从栈空间指向堆空间的指针删除,从而导致析构函数无法 delete


实现代码

// Student.cc
#include <string.h>

class Student
{
public:
    Student(int id, const char *name)	// 构造函数
        :_id(id)
        , _name(new char[strlen(name) + 1]())
    {
        strcpy(_name, name);
    }
    
    ~Student()	// 析构函数
    {
        delete[] _name;
        _name = nullptr;
    }
    
    // 默认为静态函数,所以没有 this 指针
    void *operator new(size_t sz)	// operator new 函数的重载
    {
        // 1. 调用名为 operator new的标准库函数
        // 分配足够大的原始的未类型化的内存,以保存指定类型的一个对象
        void *pret = malloc(sz);	// 只能用 malloc 以避免死循环
        
        return pret;
    }
    
    // 默认为静态函数,所以没有 this 指针
    void operator delete(void *pointer)	// operator delete 函数的重载
    {
        free(pointer);
    }
    
private:
    int _id;
    char *_name;
    
}


int main()
{
    Student *pstu = new Student(123, "LiMing");
    // 函数体
    
    delete pstu;
    
    return 0;
}



实现只能生成栈对象

tips:栈区(stack) - 自动分配释放,存放函数的参数值,局部变量的值等

堆对象: // new 表达式的工作步骤

1. operator new 函数 ——> 私有
2. 构造函数
3. 指针

所以将 operator new/delete 设置为私有


实现代码

private:
    void *operator new(size_t sz)	// operator new 函数的重载
    {
        // 1. 调用名为 operator new 的标准库函数
        // 分配足够大的原始的未类型化的内存,以保存指定类型的一个对象
        void *pret = malloc(sz);
        
        return pret;
    }

	// 可省去对 operator delete 的私有操作
    void operator delete(void *pret)	// operator delete 函数的重载
    {
        free(pret);
    }


实现只能生成堆对象

tips:堆区(heap) - 由程序员分配释放,若不释放,程序结束时由 OS 回收

栈对象必须确保构造函数析构函数同时为 public

将 析构函数 设为 private ——> delete 失效 ——> 在 class 的 public 中创建 destory 函数,调用 private 的析构函数


实现代码

public:
	// 由于要调用 this,所以不能将该函数设置为 static
	void destory()
    {
        /* this->~Student();	// 用 this 指向本身,从而调用 ~Student() 函数
        						// 但是删除仍不干净,operator delete 函数未被调用 */
        delete this;
    }

private:
    ~Student()	// 析构函数
    {
        delete[] _name;
        _name = nullptr;
    }




四、类域

作用域分为类作用域类名的作用域以及对象的作用域


类的定义没有生存周期的概念,但类定义有作用域和可见域。

使用类名创建对象时,前提是类名可见,而类名是否可见,取决于类定义的可见域。


tip:一个重点为:当内部作用域声明了同外部作用域函数名相同的函数时(函数名相同即可!返回类型 和 形参列表 无所谓!),会隐藏外部作用域的所有同名函数。除非将外部作用域的同名函数定义为 virtual 虚函数。这一点在 继承和多态 中集中体现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值