类和对象
C++面向对象三大特性:封装、继承、多态
C++认为万事万物都皆为对象,对象上有其属性和行为
1.封装
1.1 封装的意义
(1)将属性和行为作为一个整体,表现生活中的事务
语法:class 类名{ 访问权限: 属性 / 行为 };
(2)类在设计时,可以把属性和行为放在不同的权限下,加以控制
pubilc 公共权限 , 类内可以访问,类外可以访问
protected 保护权限 ,类内可以访问,类外不能访问,子类可以访问父类
private 私有权限 ,类内可以访问,类外不能访问,子类不能访问父类
#include<iostream>
using namespace std;
//圆周率
const double PI = 3.14;
//设计一个圆类,求圆的周长
class Circle
{
//公共权限
public:
//属性 成员属性、成员变量
int m_r;//半径
//行为
double calculateZC()//获取圆的周长
{
m_Password = 1234;
return 2 * PI * m_r;
}
//私有权限
private:
int m_Password;//密码
};
int main() {
//通过圆类 创建具体的圆(对象)实例化
Circle circle;
//给圆对象 的属性进行赋值
circle.m_r = 10;
//调用圆对象的 行为获得周长
double ZC = circle.calculateZC();
system("pause");
return 0;
}
1.2 struct 和 class 区别
在C++中struct和class唯一的区别在于 默认的访问权限不同
区别:
struct默认权限为公共
class默认权限为私有
1.3 成员属性设置为私有
优点1:将所有成员属性设置为私有,可以自己控制读写权限
有点2:对于写权限,我们可以检测数据的有效性
#include<iostream>
#include<string>
using namespace std;
class Person
{
public:
//设置姓名
void SetName(string name)
{
m_Name = name;
}
//获取姓名
string getName()
{
return m_Name;
}
//获取年龄
int getAge()
{
return m_Age;
}
void setAge(int age)
{
if (age < 0 || age>150)
{
cout << "年龄输入有误啊" << endl;
return;
}
m_Age = age;
}
//写入爱人
void SetLover(string lover)
{
m_Lover = lover;
}
private:
//姓名 可读可写
string m_Name;
//年龄 只读
int m_Age;
//爱人 只写
string m_Lover;
};
class Cube
{
public:
//设置长宽高
void set_LWH(int L, int W, int H)
{
m_L = L;
m_W = W;
m_H = H;
}
int* get_LWH()
{
int LWH[] = { m_L,m_W,m_H };
return LWH;
}
double get_Area()
{
return m_L * m_W * 2 + m_L * m_H * 2 + m_H * m_W * 2;
}
double get_Vol()
{
return m_L * m_W * m_H;
}
private:
//长宽高
double m_L;
double m_W;
double m_H;
};
//利用全局函数判断 两个立方体是否相等
bool isSame(Cube& c1, Cube& c2)
{
const int* a1 = c1.get_LWH();
const int* a2 = c2.get_LWH();
if (*a1==*a2&&*(a1+1)==*(a2+1)&& *(a1 + 2) == *(a2 + 2))
{
return true;
}
else
{
return false;
}
}
int main() {
/*Person p;
p.SetName("Mike");
cout << "姓名:" << p.getName() << endl;*/
Cube cube1;
cube1.set_LWH(10, 20, 30);
double area1 = cube1.get_Area();
double vol1 = cube1.get_Vol();
Cube cube2;
cube2.set_LWH(10, 20, 30);
double area2 = cube2.get_Area();
double vol2 = cube2.get_Vol();
isSame(cube1, cube2);
system("pause");
return 0;
}
2.对象的初始化和清理
2.1 构造函数和析构函数
构造函数语法:类名(){}
1.构造函数,没有返回值也不写void
2.函数名称与类名相同
3.构造函数可以有参数,因此可以发生重载
4.程序在调用对象时候会自动调用构造函数,无须手动调用,而且只会调用一次
析构函数语法:~类名(){}
1.析构函数,没有返回值也不写void
2.函数名称与类名相同,在名称上加上符号~
3.析构函数不可以有参数,因此不能发生重载
4.程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
#include<iostream>
using namespace std;
#include<string>
//函数构造的分类及调用
//参数分类: 无参构造(默认构造)
//类型分类: 普通构造 拷贝构造
class Person
{
public:
Person();
Person(int a);
Person(const Person& p);
~Person();
private:
int age;
};
Person::Person()
{
cout << "构造函数调用" << endl;
}
Person::Person(int a)
{
cout << "有参构造函数调用" << endl;
}
//拷贝构造函数
Person::Person(const Person& p)
{
age = p.age;
cout << "拷贝构造函数调用" << endl;
}
Person::~Person()
{
cout << "析构函数调用" << endl;
}
//构造和析构都是必须有的实现,如果我们自己不提供,编译器会提供一个空实现的构造和析构
void test01()
{
//调用默认构造函数时候,不要加()
Person p1;//在栈上的数据,test01执行完毕后,释放这个对象
//1.括号法
Person p2(10);
Person p3(p2);
//2.显示法
Person p4 = Person(10);
Person p5 = Person(p2);
//3.隐式转换法
Person p6 = 10;
}
int main()
{
test01();
//Person p;
system("pause");
return 0;
}
2.2 拷贝构造函数调用时机
2.3 构造函数调用规则
2.4 深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
总结:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
#include<iostream>
using namespace std;
class Person
{
public:
Person()
{
cout << "Person的默认构造函数" << endl;
}
Person(int age,int height)
{
m_Age = age;
m_Height = new int(height);
cout << "Person的有参构造函数" << endl;
}
//自己实现拷贝构造函数 解决浅拷贝带来的问题
Person(const Person& p)
{
cout << "Person 拷贝构造函数调用" << endl;
m_Age = p.m_Age;
//m_Height = p.m_Height; //编译器默认实现的是这行代码
//深拷贝操作
m_Height = new int(*p.m_Height);
}
~Person()
{
//析构代码,将堆区开辟数据做释放操作
if (m_Height!=NULL)
{
delete m_Height;
m_Height = NULL;
}
cout << "Person的析构函数" << endl;
}
int m_Age;//年龄
int* m_Height;
};
void test01()
{
Person p1(18,160);
cout << "p1的年龄为 " << p1.m_Age << endl;
Person p2(p1);
cout << "p2的年龄为 " << p2.m_Age << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
2.5 初始化列表
class Test
{
public:
Test(int a, int b, int c) :m_A(a), m_B(b), m_C(c)
{
}
int m_A;
int m_B;
int m_C;
};
int main()
{
Test t(30, 20, 10);
cout << t.m_A << endl;
//test01();
system("pause");
return 0;
}
2.6 类对象作为类成员
class A{}
class B{A a;}
当其他类对象作为本类成员,构造时候先构造类对象,再构造自身,析构的顺序与构造相反
2.6 静态成员
3. C++对象模型和this指针
3.1 成员变量和成员函数分开存储
在C++中,类内的成员变量和成员函数分开存储
只有非静态成员变量才属于类的对象上
class A
{
public:
A() {}
//非静态成员变量占对象空间
int m_A;
//静态成员变量不占对象空间
static int m_B;
//函数也不占对象空间,所有函数共享一个函数实例
void func()
{
cout << "mA:" << this->m_A << endl;
}
};
Person p;
//空对象占用内存空间为:1
//C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置
//每个空对象也应该有一个独一无二的内存地址
3.2 this指针概念
3.2 const 修饰成员函数
class Person
{
public:
//this指针的本质 是指针常量 指针的指向是不可以修改的
//const Person * const this
//在成员函数后面加const 修饰的是this指向,让指针指向的值也不可以修改
void showPerson()const//常函数
{
//this->m_A = 10;
this->m_B = 100;
}
int m_A;
//mutable修饰的是特殊变量,即使在常函数中,也可以修改这个值
mutable int m_B;
};
void test02()
{
const Person p;//在对象前加consr,变为常对象
p.m_B = 100;
//常对象只能调用常函数
p.showPerson();
}
4. 友元
友元的目的就是让一个函数或者类 访问另一个类中私有成员
友元的关键字为 friend
友元的三种实现:全局函数做友元、类做友元、成员函数做友元
5.运算符重载
//成员函数重载+运算符
Person operator+(Person& p)
{
Person temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}
//全局函数重载+运算符
Person operator+(Person& p1, Person& p2)
{
Person temp;
temp.m_A = p1.m_A + p2.m_A;
temp.m_B = p1.m_B + p2.m_B;
return temp;
}
6.继承
继承的主要目的----减少代码量
class language
{
public:
void header()
{
cout << "首页、公开课、登录、注册" << endl;
}
};
class python:public language
{
public:
void content()
{
cout << "python" << endl;
}
};
class cplus :public language
{
public:
void content()
{
cout << "cplus" << endl;
}
};
继承中的构造和析构顺序如下:
先构造父类,再构造子类,析构的顺序与构造相反,先析构子类,再析构父类
当子类和父类存在同名属性,或者同名方法时,直接调用是调用子类对象,访问父类对象需要加 Base::作用域:
Son s;
cout<<"son下的成员"<<s.m_A<<endl;
cout<<"Base下的成员"<<s.Base::m_A<<endl;
cout<<"son下的方法"<<s.func()<<endl;
cout<<"Base下的方法"<<s.Base::func()<<endl;
菱形继承:
两个派生类继承同一个基类,又有某个类同时继承这两个派生类,这种继承被称为菱形继承,或者砖石继承。
7.多态
7.1 多态的基本概念
多态是C++面向对象三大特性之一
多态分为两类
1.静态多态:函数重载 和 运算符重载属于静态多态,复用函数名
2.动态多态:派生类和虚函数实现运行时多态
静态多态和动态多态的区别:
1.静态多态的函数地址早绑定 - 编译阶段确定函数地址
2.动态多态的函数地址晚绑定 - 运行阶段确定函数地址
#include<iostream>
using namespace std;
class Animal {
public:
//虚函数
virtual void speak() {
cout << "动物在说话" << endl;
}
};
class Cat:public Animal {
public:
void speak() {
cout << "小猫在说话" << endl;
}
};
class Dog :public Animal {
public:
void speak() {
cout << "小狗在说话" << endl;
}
};
//执行说话的函数
//地址早绑定 在编译阶段确定函数地址
//如果想执行让猫说话 那么这个函数地址就不能提前绑定 需要在运行阶段进行绑定 地址晚绑定
//动态多态满足条件
//1.有继承关系
//2.子类重写父类的虚函数
//动态多态使用
//父类的指针或引用 执行子类对象
void doSpeak(Animal &animal) {
animal.speak();
}
void test001() {
Cat cat;
doSpeak(cat);
Dog dog;
doSpeak(dog);
}
//利用多态实现计算器
//多态好处:1、组织结构清晰 2、可读性强 3、对于前期和后期扩展以及维护性高
//实现计算器 抽象类
class AbstractCalculator {
public:
virtual int getResult() {
return 0;
}
int m_Num1;
int m_Num2;
};
//实现计算器 加法类
class AddCalculator:public AbstractCalculator {
int getResult() {
return m_Num1 + m_Num2;
}
};
void test002() {
AbstractCalculator* absCal = new AddCalculator;
absCal->m_Num1 = 100;
absCal->m_Num2 = 200;
cout << absCal->getResult() << endl;
delete absCal;
}
int main() {
test001();
test002();
system("pause");
return 0;
}
7.2 纯虚函数和抽象类
纯虚函数语法: virtual 返回值类型 函数名 (参数列表) = 0 ;
例如: virtual void func() = 0 ;
当类中有了 纯虚函数,这个类也称为 抽象类
抽象类特点:1、无法实例化对象 2、子类必须重写父类中的虚函数,否则也属于抽象类
7.3 虚析构和纯虚析构
7.4 多态案例
#include<iostream>
#include<string>
using namespace std;
//抽象的CPU类
class CPU {
public:
virtual void caculate() = 0;
};
//抽象的显卡类
class GPU {
public:
virtual void display() = 0;
};
//抽象的内存条类
class Memory {
public:
virtual void storage() = 0;
};
class Computer {
public:
Computer(CPU* cpu, GPU* gpu, Memory* memory) {
m_cpu = cpu;
m_gpu = gpu;
m_memory = memory;
}
void work() {
m_cpu->caculate();
m_gpu->display();
m_memory->storage();
}
//提供析构函数 释放3个电脑零件
~Computer(){
if (m_cpu!=NULL)
{
delete m_cpu;
m_cpu = NULL;
}
if (m_gpu != NULL)
{
delete m_gpu;
m_gpu = NULL;
}
if (m_memory != NULL)
{
delete m_memory;
m_memory = NULL;
}
}
private:
CPU* m_cpu;
GPU* m_gpu;
Memory* m_memory;
};
class Intel :public CPU, public GPU, public Memory {
public:
void caculate() { cout << "Intel的CPU进行计算" << endl; }
void display() { cout << "Intel的显卡进行显示" << endl; }
void storage() { cout << "Intel的内存条进行存储" << endl; }
};
class Lenovo :public CPU, public GPU, public Memory {
public:
void caculate() { cout << "Lenovo的CPU进行计算" << endl; }
void display() { cout << "Lenovo的显卡进行显示" << endl; }
void storage() { cout << "Lenovo的内存条进行存储" << endl; }
};
class otherCPU :public CPU {
public:
void caculate() { cout << "Other的CPU进行计算" << endl; }
};
class otherGPU :public GPU {
public:
void display() { cout << "Other的显卡进行显示" << endl; }
};
class otherMemory :public Memory {
public:
void storage() { cout << "Other的内存条进行存储" << endl; }
};
void test11()
{
//屏蔽段的代码,delete时会报错,不知道原因
/*CPU* intelCPU = new Intel;
GPU* intelgpu = new Lenovo;
Memory* intelmem = new Intel;
Computer* computer1 = new Computer(intelCPU, intelgpu, intelmem);
computer1->work();
delete computer1;
Computer* computer2 = new Computer(new Intel, new Lenovo, new Intel);
computer2->work();
delete computer2;*/
Computer* computer3 = new Computer(new otherCPU, new otherGPU, new otherMemory);
computer3->work();
delete computer3;
}
int main() {
test11();
system("pause");
return 0;
}
8.文件操作
8.1 文本文件
文件类型分为两种:
1、文本文件 - 文件以文本的ASCII码形式存储在计算机中
2、二进制文件 - 文件以文本的二进制形式存储在计算机中,用户一般不能直接读它们
操作文件的三大类:
1、ofstream : 写操作
2、ifstream :读操作
3、fstream : 读写操作
8.1.1 写文件
写文件步骤如下:
1.包含头文件 #include
2.创建流对象 ofstream ofs;
3.打开文件 ofs.open(“文本路径”,打开方式);
4.写数据 ofs<<“写入的数据”;
5.关闭文件 ofs.close();
8.1.2 读文件
读文件步骤如下:
1.包含头文件 #include
2.创建流对象 ifstream ifs;
3.打开文件 ifs.open(“文本路径”,打开方式);
4.读数据 四种方式读取
5.关闭文件 ifs.close();
#include<iostream>
using namespace std;
#include<fstream>
#include<string>
//写文件
void fun_write()
{
//1.包含头文件 fstream
//2.创建流对象
ofstream ofs;
//3.指定打开方式
ofs.open("test.txt", ios::out);
//4.写内容
ofs << "姓名:张三" << endl;
//5.关闭文件
ofs.close();
}
void fun_read()
{
//1.包含头文件 fstream
//2.创建流对象
ifstream ifs;
//3.指定打开方式
ifs.open("test.txt", ios::out);
if (!ifs.is_open())
{
cout << "文件打开失败" << endl;
return;
}
//4.读数据 -- 四种方式
第一种
//char buf[1024] = { 0 };
//while (ifs>>buf)
//{
// cout << buf << endl;
//}
//第二种
char buf[1024] = { 0 };
while (ifs.getline(buf,sizeof(buf)))
{
cout << buf << endl;
}
第三种
//string buf;
//while (getline(ifs,buf))
//{
// cout << buf << endl;
//}
//第四种
/*char c;
while ((c=ifs.get())!=EOF)
{
cout << c;
}*/
//5.关闭文件
ifs.close();
}
int main() {
fun_write();
fun_read();
system("pause");
return 0;
}
8.2 二进制文件
写文件:
二进制方式写文件主要利用流对象调用成员函数write
函数原型:ostream& write(const char * buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间,len时读写的字节数
读文件:
二进制方式读文件主要利用流对象调用成员函数read
函数原型:istream& read(char * buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间,len时读取的字节数
#include<iostream>
using namespace std;
#include<fstream>
//二进制文件 写文件
class Person {
public:
char m_Name[64];
int m_Age;
};
void test_write()
{
ofstream ofs("person.txt", ios::out || ios::binary);
Person p = { "张三",18 };
ofs.write((const char*)&p, sizeof(Person));
ofs.close();
}
void test_read()
{
ifstream ifs;
ifs.open("person.txt", ios::in || ios::binary);
if (!ifs.is_open())
{
cout << "文件打开失败" << endl;
return;
}
Person p;
ifs.read((char*)&p, sizeof(Person));
cout << p.m_Name << "--" << p.m_Age << endl;
ifs.close();
}