Sirius' blog Sirius' blog
首页
  • 学习笔记

    • 《C++》
    • 《MATLAB》
    • 《Python》
  • 学习笔记

    • 《Git》
    • 《CMake》
  • 技术文档
  • 博客搭建
  • 学习
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Sirius0v0

怕什么真理无穷,进一寸有一寸的欢喜
首页
  • 学习笔记

    • 《C++》
    • 《MATLAB》
    • 《Python》
  • 学习笔记

    • 《Git》
    • 《CMake》
  • 技术文档
  • 博客搭建
  • 学习
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 基础

    • 程序的内存模型
    • CC++指针
    • CC++引用
    • CC++类与对象
      • 对象的初始化和清理
        • 构造函数与析构函数
        • 拷贝构造函数的调用时机
        • 深拷贝与浅拷贝
        • 静态成员
      • 空指针访问成员函数
      • const修饰成员函数
      • 友元
    • CC++运算符重载
    • CC++继承
    • CC++多态
    • CC++文件操作
  • Cpp小记
  • C++学习笔记-220705-V1
  • 进阶

  • 奇思妙用

  • 库的使用

  • 《C++》学习笔记
  • 基础
Sirius0v0
2023-07-05
目录

CC++类与对象

# C/C++: 类与对象

C++面向对象的三大特性:封装、继承和多态

# 对象的初始化和清理

# 构造函数与析构函数

构造函数按参数分可分为无参构造和有参构造,按类型分可分为普通构造和拷贝构造;

class Person
{
public:
    Person();
    Person(int a);
    // 拷贝构造
    Person(const Person &p);
};

类的调用方法:

// 1. 调用默认构造函数
Person p1;	// 注意不要加括号,否则会被认为是函数声明;

// 2. 显示调用
Person p2(10);
Person p3(p2);
// 匿名对象
Person(10);	// 执行结束后系统立即回收销毁
// Person(p3); 		
// 注意不要利用拷贝构造函数初始化匿名对象,会被当做是 Person p3;但是后紧接一个成员函数则不会报错,如Person(p3).someMethod();
// 会产生 重定义 的错误

// 3. 隐式转换法
Person p4 = 10;		// 相当于 Person p4 = Person(10);
Person p5 = p4;

# 拷贝构造函数的调用时机

  • 使用一个已经创建完毕的对象来初始化一个新对象;
  • 值传递的方式给函数参数传值;
  • 以值的方式返回局部对象。

默认情况下,C++编译器至少给一个类添加如下四个函数:

  • 默认构造函数(空)
  • 默认析构函数(空)
  • 默认拷贝构造函数(对属性进行值拷贝)
  • 赋值运算符operator=对属性进行值拷贝

# 深拷贝与浅拷贝

在类的拷贝过程中属于浅拷贝,当类中有开辟在堆区的指针变量时,浅拷贝会将地址原封不动地复制过去,在析构函数中则会出现对同一块地址多次释放的风险,因而,需要自己实现拷贝构造函数以解决浅拷贝带来的问题。

Person(const Person &p)
{
    m_Height = new int(*p.m_Height);
}

如果有属性在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题

有关于该问题,有几条前人总结的法则,可以作为参考:

  • 如果定义了析构函数,那必须同时定义或删除拷贝构造函数和拷贝赋值函数;
  • 如果一个类定义了拷贝构造函数,那么必须同时定义或删除拷贝赋值函数,负责出错,如果删除将导致低效;
  • 如果类定义了移动构造函数,那么必须同时定义或删除移动赋值函数,否则出错,删除将导致低效;
  • 如果类定义了拷贝构造函数或拷贝赋值函数,则最好同时定义移动构造函数或移动赋值函数,否则低效。

如何避免没有必要的拷贝?:使用常引用const X& x

当然,对于原始数据类型,诸如int,float等,容器并不大,没有必要使用常引用。

如何避免不经意的隐式拷贝?:将拷贝构造函数声明为explicit,例如某函数void show(Pig pig);,如果使用show(pig);调用,则会编译错误,只能通过show(Pig{pig});语法强制拷贝。

# 静态成员

静态成员分为:

  • 静态成员变量

    • 所有对象共享同一份数据
    • 编译阶段分配内存
    • 类内声明,类外初始化
    class Person
    {
        Person();
        ~Person();
        
    public:
        static int m_A;
    };
    // 类外初始化
    int Person::m_A = 100;
    

    由于共享数据,因此有以下访问数据的方式:

    // 通过对象访问
    Person p1;
    p1.m_A = 100;
    Person p2;
    p2.m_A = 200;
    
    // 通过类名访问
    Person::m_A = 100;
    
  • 静态成员函数

    • 所有对象共享同一个函数
    • 静态成员函数只能访问静态成员变量

# 空指针访问成员函数

空指针也可以调用成员函数,但当需要访问成员变量时则会报错,为了防止此类错误,可以添加如下语句:

void show()
{
    if (this == NULL)
    {
        return;
    }
    // do something
}

# const修饰成员函数

常函数:

  • 成员函数后加const后称为常函数
  • 常函数内不可以修改成员属性
  • 成员属性声明时加关键字mutable后,常函数中就可以修改
class Person
{
public:
    void show() const
    {
        this->m_B = 100;
        // this->m_A = 100;
    }
    
    int m_A;
    mutable int m_B;
};

this指针的本质是指针常量,是对指针的修饰,当成员函数后加const后修饰的是指针的指向,即对指针和常量均修饰,所以不可以更改成员属性

常对象:

  • 声明对象前加const称该对象为常对象;
  • 常对象只能调用常函数
  • 常对象也可以修改加了关键字mutable的属性
void test()
{
    const Person p;
    // p.m_A = 100;
    p.m_B = 100;
}

# 友元

友元的三种实现:

  • 全局的函数做友元
  • 类做友元
  • 成员函数做友元

为了让全局函数friendFunc(Person* p)访问类Person中的私有成员,如下操作

class Person
{
  friend void friendFunc(Person* p);
    
public:
    int m_A;

private:
    int m_B;
};

void friendFunc(Person* p)
{
    std::cout << p.m_B;
}

同理类做友元只需在Person类中添加代码

friend class GoodGuy;

成员函数做友元需要添加

friend void GoodGuy::visit();
编辑 (opens new window)
#Cpp
上次更新: 2023/08/15, 01:36:48
CC++引用
CC++运算符重载

← CC++引用 CC++运算符重载→

最近更新
01
ipopt优化库配置及使用
07-21
02
ubuntu离线安装包的方法
07-21
03
其它控件的使用
03-05
更多文章>
Theme by Vdoing | Copyright © 2020-2024 Sirius0v0 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式