C++面向对象

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. 空指针访问成员函数
// 空指针可以访问函数
// 但是不能访问其中的属性
  1. 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; // 非法
}
  1. 类做友元
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;
};
  1. 成员函数做友元
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
  1. 左移运算符
// 重载 << 可以输出类
class Person {
public:
    int m_a;
};
void operator<< (ostream cout, Person& p) {
    // 重载<<号
	cout << p.m_a;
}
Person s1;
cout << s1; // 非法
  1. 递增运算符重载
class MyInteger {
public:
    int number;
    MyInteger() {number = 0;}
};
// 重载前置++
void operator++ (MyInteger& p) {
    p.number++;
}
// 后置++
void operator++ (int) {
    
}
MyInteger p;
p ++;
  1. 赋值运算符重载

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

  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) // 非法,需要重载==号
  1. 函数调用重载

在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();
}
posted @ 2024-12-20 20:54  菜狗小元  阅读(10)  评论(0编辑  收藏  举报