C++面向对象

类和对象

  1. 类和对象

类和对象是C++面向对象的基础,在C++中万事万物都是对象,C++利用类来实例化对象,下面是创建一个Circle类并实例化的语法:

// 创建类
class Circle {
public:
    int m_r;
    
    void getM_r {
        cout << m_r;
    }
};

// 实例化
Circle a;   

类中的变量称为属性(成员属性),类中的函数称为行为(成员函数、成员方法)。

  1. 访问权限

public: 类内可以访问,类外可以访问

protected: 类内可以访问,类外不可以访问

private: 类内可以访问,类外不可以访问

protected 和 private在继承部分会有不同。

  1. struct和class的区别

默认访问权限不同,struct默认访问权限是public,class默认权限是private

  1. 在C++中一般把成员属性设置为私有,把成员方法设置公共。下面是一个标准的C++类。

    class Person {
    private:
    string name;
    int val;

    public:
    string getName() {
    return name;
    }

     void setName(string s) {
         name = s;
     }
     
     int getVal() {
         return val;
     }
     
     void setVal(int x) {
         val = x;
     }
    

    };

    // 实例化
    Person a;
    a.setName("小明");
    a.setVal(100);

    cout << a.getName() << endl;
    cout << a.getVal();

通过成员函数来访问和设置成员属性,可以防止误操作。

  1. 对象的初始化和清理

C++通过构造函数析构函数来进行初始化和清理,这两个函数由编译器自动调用,同时存在默认实现。

构造函数:创建对象时给对象赋值

析构函数:对象销毁前,系统自动调用

// 构造函数
类名(){}
// 析构函数
~类名(){}

class Person {
private:
    int val;
    
public:
    Person() {
     	// 无参构造   
    }
    Person(int x) {
        // 含参构造
        val = x;
    }
    
    ~Person() {
        // 不允许有参数,也不允许发生重载
        // 对象销毁前自动调用
    }
};
  1. 构造函数的分类及调用

分类:无参构造(默认构造),有参构造、拷贝构造

拷贝构造

class Person {
private:
    int age;
    
public:
    Person() { // 无参构造
        
    }
    
    Person(int a) { // 有参构造
        
    }
    
	Person(const Person& p) { // 拷贝构造
        age = p.age;
    }  
};

调用方式

括号法:

Person p1; // 默认构造调用
Person p2(10); // 有参构造
Person p3(p2); // 拷贝构造

Person p(); 不允许这样调用无参构造,尽管编译不报错,但在运行时,会将这一句误解为函数声明

显示法:

Person p1; // 默认构造
Person p2 = Person(10); // 有参构造
Person p3 = Person(p2); // 拷贝构造

// Person(10) 会被认为是一个匿名对象,执行结束后,系统会立即回收匿名对象。

// Person(p3); 这样是错误的。不要利用拷贝构造,创建匿名对象,会被认为是一个对象的声明

隐式转换法:

Person p1; // 默认构造
Person p2 = 10; // 有参构造
Person p3 = p2; // 拷贝构造

new开辟的数据在堆区

  1. 浅拷贝与深拷贝

    class Person {
    private:
    string name;
    int* t;

    public:
    Person() {}
    Person(string _name, int _t) {
    name = _name;
    t = new int(_t); // 开辟在堆区
    }
    Person(const Person& p) {
    name = p.name;
    t = p.t; // 编译器提供的默认拷贝形式(浅拷贝)
    t = new int(*p.int); // 深拷贝
    }
    };

浅拷贝会造成堆区内存重复释放。

  1. 初始化列表

    class Person {
    private:
    string name;
    Person* next;

    public:
    Person(string _name, Person* _next) : name(_name), next(_next) {}
    };

9、静态成员

静态成员变量:

  • 所有对象共享同一份数据

  • 编译阶段分配内存(全局区)

  • 类内声明,类外初始化

    class person { // 类内声明
    private:
    static int a;
    };

    int person::a = 100; // 类外初始化
    // 静态成员变量也有访问权限

静态成员函数:

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

对象特性

  1. C++对象模型和this指针
  • 在C++中,类内的成员变量和成员函数分开存储

  • 只有非静态成员变量才属于类的对象上

    // 一个对象占多少内存,取决于它的成员变量
    // 空对象占一个字节

  1. this指针
  • this指针指向被调用的成员函数所属的对象

    class person {
    public:
    string name;

      person(string name) {
          // 解决名称冲突问题
          this->name = name;
      }
    

    }

  1. 空指针访问成员函数

    // 空指针可以访问函数
    // 但是不能访问其中的属性

  2. const修饰成员函数

  • 常函数内不能修改成员属性

  • 常对象只能调用常函数

  • this的本质是指针常量

    class Person {
    public:
    void showPerson() const {
    m_A = 100; // 非法
    m_B = 200; // 合法
    }

      int m_A;
      mutable int m_B;
    

    }

    void test02() {
    const Person p;
    p.m_A = 100; // 非法
    p.m_B = 200; // 合法
    }

友元

在程序中,有些私有属性,也想让类外的特殊函数访问

  1. 全局函数做友元

    class Building {
    friend void fun(Building build); // 声明友元全局函数
    public:
    Building() {}
    public:
    string sittingRoom;
    private:
    string room;
    };

    void fun(Building Build) {
    cout << build.sittingRoom; // 合法
    cout << build.room; // 非法
    }

  2. 类做友元

    class Goodgay {
    public:
    Building* build = new Building();

     void visit() {
         cout << build->room;  // 非法
         cout << build->sittingRoom; // 合法
     }
    

    }
    class Building {
    friend class Goodgay;
    public:
    Building() {}
    public:
    string sittingRoom;
    private:
    string room;
    };

  3. 成员函数做友元

    friend void Goodgay::visit();

运算符重载

对已有的运算符重新进行定义,赋予其另一种功能

  1. 加号重载

    class Person {
    public:
    int m_a;
    }
    Person operator+ (Person& p1, Person& p2) {
    // 重载+号
    Person temp;
    temp.m_a = p1.m_a + p2.m_a;
    return temp;
    };
    Person s1;
    s1.m_a = 10;
    Person s2;
    s2.m_a = 20;
    Person p3 = p1 + p2; // 非法操作,编译器无法理解P1 + p2

  2. 左移运算符

    // 重载 << 可以输出类
    class Person {
    public:
    int m_a;
    };
    void operator<< (ostream cout, Person& p) {
    // 重载<<号
    cout << p.m_a;
    }
    Person s1;
    cout << s1; // 非法

  3. 递增运算符重载

    class MyInteger {
    public:
    int number;
    MyInteger() {number = 0;}
    };
    // 重载前置++
    void operator++ (MyInteger& p) {
    p.number++;
    }
    // 后置++
    void operator++ (int) {

    }
    MyInteger p;
    p ++;

  4. 赋值运算符重载

编译器会对'='进行默认重载,对属性进行值拷贝,可能会带来浅拷贝的问题

  1. 关系运算符重载

    class Person {
    public:
    int age;
    string name;
    };

    bool operator== (Person& p1, Person& p2) {
    if (p1.age == p2.age && p1.name == p2.name) {
    return ture;
    } else {
    return false;
    }
    }

    Person p1(10, "tom");
    Person p2(100, "tom");

    if (p1 == p2) // 非法,需要重载==号

  2. 函数调用重载

在STL中常用函数调用重载,也叫仿函数

继承

  1. 语法

class 子类 : 继承方式 父类

class son : public father {
public:
    cout << "这是一个子类" << endl;
};
  1. 继承方式

父类中私有的内容,子类无法访问

  • 公共继承

父类中public变为public,protected变为protected

  • 保护继承

父类中public变为protected,protected变为protected

保护权限,类外不可以访问

  • 私有继承

父类中public变为private,protected变为private

  1. 继承中的对象模型

父类中所有非静态成员属性都会被子类继承下去

父类中私有成员属性,是被编译器隐藏了

  1. 构造和析构顺序

子类继承父类后,当创建子类对象时,也会调用父类的构造

先构造父类,再构造子类,先析构子类,再析构父类

  1. 继承中同名的处理方式
  • 访问子类 直接访问
  • 访问父类 +作用域
  1. 同名静态成员处理

跟5一样,子类直接访问,父类+作用域

子类中出现和父类同名静态成员函数,会隐藏掉父类的所有静态成员函数

son.father::fun()

  1. 多继承语法

语法:class 子类 : 继承方式 父类1,继承方式 父类2

  1. 虚继承

class sheep : virtual public animal {

};

菱形继承导致了子类继承同样的数据,造成内存浪费

virtual可以解决菱形继承的问题

vbptr 虚基类指针 指向 vbtable 虚基类表

多态

  • 静态多态:函数重载 运算符重载 复用函数名
  • 动态多态:派生类和虚函数实现运行时多态

静态多态的函数地址早绑定,编译阶段确定函数的地址

动态多态的函数地址晚绑定,运行阶段确定函数的地址

父类的指针,可以直接指向子类

class animal {
public:
  	void speak() {
        cout << "动物在说话"
    }  
};

class cat {
public:
    void speak() {
        cout << "猫在说话" << endl;
    }
};

void doSpeak(animal& a) {
    a.speak();
}

int main() {
    cat a;
    doSpeak(a);
}
// 输出->动物在说话
将父类中的函数换位 virtual void speak()
// 输出->猫在说话

动态多态的两个条件

  • 存在继承关系
  • 子类重写父类的虚函数
  • 父类的指针或引用指向子类对象

底层原理

virtual函数中存在一个vfptr-虚函数表指针

指向vftable

纯虚函数

当类中有了纯虚函数,这个类就是抽象类

抽象类不可以实例化

子类必须重写抽象类中的纯虚函数,否则也属于抽象类

class animal {
public:
    virtual void fun() = 0; // 纯虚函数
};

虚析构和纯虚析构

把父类中的析构函数改为虚析构

利用虚析构可以解决父类指针释放子类对象,释放不干净的问题

纯虚析构

virtual ~animal() = 0;

需要在类外,重写析构函数

文件读写

文件流管理

1、文本文件

文件以文本的ASCII码形式存储在计算机中

2、二进制文件

文本以二进制形式存储在计算机中,用户一般不能直接读懂

  • ofstream 写操作
  • ifstream 读操作
  • fstream 读写操作

写文件步骤:

  1. 包含头文件
  2. 创建流对象
  3. 打开文件

ios::in 读文件的方式

ios::out 写文件的方式

ios::trunc 如果文件存在先删除,再创建

  1. 写数据

  2. 关闭文件

    #include <iostream>
    #include <fstream>

    using namespace std;

    // 写文件
    void test01() {
    ofstream ofs;

     ofs.open("文件路径", ios::out);
     
     ofs << "你好,文本" << endl;
     
     ofs.close();
    

    }

    void test02() {
    // 读文件
    ifstream ifs;

     ifs.open("hello.txt", ios::in);
     
     if (!ifs.is_open()) {
         cout << "打开失败" << endl;
         return;
     }
     
     char buf[1024] = {0};
     while (ifs >> buf) {
         cout << buf << endl;
     }
     
     string buf;
     while (getline(ifs, buf)) {
         cout << buf << endl;
     }
     
     char c;
     while ((c = ifs.get() != EOF)) {
         cout << c;
     }
    

    }

    int main() {
    test01();
    }

// 二进制方式读写

二进制写

#include <iostream>
#include <fstream>

using namespace std;

class Person {
public:
    char m_Name[64];
    int m_Age;
};

void test01() {
   	ofstream ofs;
    ofs.open("wang.txt", ios::out | ios::binary);
    Person p;
    p.m_Name = "zhangsan";
    p.m_Age = 18;
    
    ofs.write((const char*)&p, sizeof(Person));
    
    ofs.open();
}

int main() {
    test01();
}

二进制读

#include <iostream>
#include <fstream>

using namespace std;

class Person {
public:
    char m_Name[64] = "zhaolei";
    int m_Age;
};

void test02() {
   	ofstream ofs;
    ofs.open("wang.txt", ios::out | ios::binary);
    Person p;
    p.m_Name = "zhangsan";
    p.m_Age = 18;
    
    ofs.write((const char*)&p, sizeof(Person));
    
    ofs.open();
}

int main() {
    test01();
}
相关推荐
Zer0_on30 分钟前
C++string类
开发语言·c++
Lenyiin34 分钟前
02.01、移除重复节点
c++·算法·leetcode
禁默37 分钟前
深入浅出:Java 抽象类与接口
java·开发语言
小万编程2 小时前
【2025最新计算机毕业设计】基于SSM的医院挂号住院系统(高质量源码,提供文档,免费部署到本地)【提供源码+答辩PPT+文档+项目部署】
java·spring boot·毕业设计·计算机毕业设计·项目源码·毕设源码·java毕业设计
白宇横流学长2 小时前
基于Java的银行排号系统的设计与实现【源码+文档+部署讲解】
java·开发语言·数据库
123yhy传奇2 小时前
【学习总结|DAY027】JAVA操作数据库
java·数据库·spring boot·学习·mybatis
想要打 Acm 的小周同学呀2 小时前
亚信科技Java后端外包一面
java·求职·java后端
lishiming03086 小时前
TestEngine with ID ‘junit-jupiter‘ failed to discover tests 解决方法
java·junit·intellij-idea
HEU_firejef6 小时前
设计模式——工厂模式
java·开发语言·设计模式
Kobebryant-Manba6 小时前
单元测试学习2.0+修改私有属性
java·单元测试·log4j