C++面向对象
C++核心
类和对象
- 类和对象
类和对象是C++面向对象的基础,在C++中万事万物都是对象,C++利用类来实例化对象,下面是创建一个Circle类并实例化的语法:
// 创建类
class Circle {
public:
int m_r;
void getM_r {
cout << m_r;
}
};
// 实例化
Circle a;
类中的变量称为属性(成员属性),类中的函数称为行为(成员函数、成员方法)。
- 访问权限
public: 类内可以访问,类外可以访问
protected: 类内可以访问,类外不可以访问
private: 类内可以访问,类外不可以访问
protected 和 private在继承部分会有不同。
- struct和class的区别
默认访问权限不同,struct默认访问权限是public,class默认权限是private
- 在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();
通过成员函数来访问和设置成员属性,可以防止误操作。
- 对象的初始化和清理
C++通过构造函数和析构函数来进行初始化和清理,这两个函数由编译器自动调用,同时存在默认实现。
构造函数:创建对象时给对象赋值
析构函数:对象销毁前,系统自动调用
// 构造函数
类名(){}
// 析构函数
~类名(){}
class Person {
private:
int val;
public:
Person() {
// 无参构造
}
Person(int x) {
// 含参构造
val = x;
}
~Person() {
// 不允许有参数,也不允许发生重载
// 对象销毁前自动调用
}
};
- 构造函数的分类及调用
分类:无参构造(默认构造),有参构造、拷贝构造
拷贝构造:
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开辟的数据在堆区
- 浅拷贝与深拷贝
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); // 深拷贝
}
};
浅拷贝会造成堆区内存重复释放。
- 初始化列表
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; // 类外初始化
// 静态成员变量也有访问权限
静态成员函数:
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量
对象特性
- C++对象模型和this指针
- 在C++中,类内的成员变量和成员函数分开存储
- 只有非静态成员变量才属于类的对象上
// 一个对象占多少内存,取决于它的成员变量
// 空对象占一个字节
- this指针
- this指针指向被调用的成员函数所属的对象
class person {
public:
string name;
person(string name) {
// 解决名称冲突问题
this->name = name;
}
}
- 空指针访问成员函数
// 空指针可以访问函数
// 但是不能访问其中的属性
- 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; // 合法
}
友元
在程序中,有些私有属性,也想让类外的特殊函数访问
- 全局函数做友元
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; // 非法
}
- 类做友元
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;
};
- 成员函数做友元
friend void Goodgay::visit();
运算符重载
对已有的运算符重新进行定义,赋予其另一种功能
- 加号重载
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
- 左移运算符
// 重载 << 可以输出类
class Person {
public:
int m_a;
};
void operator<< (ostream cout, Person& p) {
// 重载<<号
cout << p.m_a;
}
Person s1;
cout << s1; // 非法
- 递增运算符重载
class MyInteger {
public:
int number;
MyInteger() {number = 0;}
};
// 重载前置++
void operator++ (MyInteger& p) {
p.number++;
}
// 后置++
void operator++ (int) {
}
MyInteger p;
p ++;
- 赋值运算符重载
编译器会对'='进行默认重载,对属性进行值拷贝,可能会带来浅拷贝的问题
- 关系运算符重载
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) // 非法,需要重载==号
- 函数调用重载
在STL中常用函数调用重载,也叫仿函数
继承
- 语法
class 子类 : 继承方式 父类
class son : public father {
public:
cout << "这是一个子类" << endl;
};
- 继承方式
父类中私有的内容,子类无法访问
- 公共继承
父类中public变为public,protected变为protected
- 保护继承
父类中public变为protected,protected变为protected
保护权限,类外不可以访问
- 私有继承
父类中public变为private,protected变为private
- 继承中的对象模型
父类中所有非静态成员属性都会被子类继承下去
父类中私有成员属性,是被编译器隐藏了
- 构造和析构顺序
子类继承父类后,当创建子类对象时,也会调用父类的构造
先构造父类,再构造子类,先析构子类,再析构父类
- 继承中同名的处理方式
- 访问子类 直接访问
- 访问父类 +作用域
- 同名静态成员处理
跟5一样,子类直接访问,父类+作用域
子类中出现和父类同名静态成员函数,会隐藏掉父类的所有静态成员函数
son.father::fun()
- 多继承语法
语法:class 子类 : 继承方式 父类1,继承方式 父类2
- 虚继承
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 读写操作
写文件步骤:
- 包含头文件
- 创建流对象
- 打开文件
ios::in 读文件的方式
ios::out 写文件的方式
ios::trunc 如果文件存在先删除,再创建
-
写数据
-
关闭文件
#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();
}