结构体与类的区别
//C++ 中struct和class的区别
class c1
{
int m_A;//默认权限是 私有
};
struct c2
{
int m_A;//默认权限为 公共
};
void test06()
{
c1 p1;
//p1.m_A;//无法访问
c2 p2;
p2.m_A;
}
总结:
1.结构体是一种值类型,而类是引用类型。值类型用于存储数据的值,引用类型用于存储对实际数据的引用。那么结构体就是当成值来使用的,类则通过引用来对实际数据操作。
2. 结构体使用栈存储(Stack Allocation),而类使用堆存储(Heap Allocation)
栈的空间相对较小.但是存储在栈中的数据访问效率相对较高.
堆的空间相对较大.但是存储在堆中的数据的访问效率相对较低.
3.类是反映现实事物的一种抽象,而结构体的作用只是一种包含了具体不同类别数据的一种包装,结构体不具备类的继承多态特性
4.结构体赋值是 直接赋值的值. 而对象的指针 赋值的是对象的地址
5.Struct变量使用完之后就自动解除内存分配,Class实例有垃圾回收机制来保证内存的回收处理。
6.结构体的构造函数中,必须为结构体所有字段赋值,类的构造函数无此限制
首先,关于隐式构造函数.我们知道,在1个类中如果我们没有为类写任意的构造函数,那么C++编译器在编译的时候会自动的为这个类生成1个无参数的构造函数.我们将这个构造函数称之为隐式构造函数 但是一旦我们为这个类写了任意的1个构造函数的时候,这个隐式的构造函数就不会自动生成了.在结构体中,就不是这样了,在结构体中隐式的构造函数无论如何都存在。所以程序员不能手动的为结构添加1个无参数的构造函数。
7.结构体中声明的字段无法赋予初值,类可以:
如何选择结构体还是类
1. 堆栈的空间有限,对于大量的逻辑的对象,创建类要比创建结构好一些
2. 结构表示如点、矩形和颜色这样的轻量对象,例如,如果声明一个含有 1000 个点对象的数组,则将为引用每个对象分配附加的内存。在此情况下,结构的成本较低。
3. 在表现抽象和多级别的对象层次时,类是最好的选择
4. 大多数情况下该类型只是一些数据时,结构时最佳的选择
构造函数
1.构造函数类型
class Person
{
public:
//无参构造函数
Person()
{
cout << "Person的无参构造函数调用" << endl;
}
//有参构造函数
Person(int a)
{
cout << "Person的有参构造函数调用" << endl;
}
//拷贝构造函数
Person(const Person &p)
{
//将传入的人身上的所有属性,拷贝到我身上
cout << "Person的拷贝构造函数调用" << endl;
age = p.age;
}
//析构函数
~Person()
{
cout << "Person的析构函数调用" << endl;
}
private:
int age;
};
void test()
{
//1.括号发
Person p1;//默认构造函数调用
Person p2(10);//有参构造函数调用
Person p3(p2);//拷贝构造函数调用
//调用无参默认构造函数时候不要加()
//Person01_() //编译器会默认为函数的声明,不会认为创建对象
//2.显示法
Person p4;
Person p5 = Person(10);//有参构造
Person p6 = Person(p5);//有参构造
Person(10);// 为匿名对象 特点:当执行结束后,系统会立即回收匿名对象(调用析构函数)
//不要利用拷贝构造函数来初始化匿名对象
//Person(p3); //编译器会认为Person(p3) 等价于 Person p3; 认为是对象的声明
//3.隐式转化法
Person p7 = 10;//相当于 Person p7 = Person(10);
Person p8 = p7;//拷贝构造
}
总结:
构造函数有三种调用方法
1.括号发
2.显示法
3.隐式转化法
注意:
①调用无参默认构造函数时候不要加() 。Person() //编译器会默认为函数的声明,不会认为创建对象
②Person(10);// 为匿名对象。 特点:当执行结束后,系统会立即回收匿名对象(调用析构函数)
③不要利用拷贝构造函数来初始化匿名对象 。Person(p3); //编译器会认为Person(p3) 等价于 Person p3; 认为是对象的声明
2.拷贝构造函数 调用时机
class Person0
{
public:
//构造函数
Person0()
{
cout << "Person0默认构造函数的调用" << endl;
}
Person0(int age)
{
m_Age = age;
cout << "Person0有参构造函数的调用" << endl;
}
Person0(const Person0 &p)
{
m_Age = p.m_Age;
cout << "Person0拷贝构造函数的调用" << endl;
}
Person0(Person0&& p)
{
cout << "Person0移动构造函数的调用" << endl;
}
//析构函数
~Person0()
{
cout << "Person0析构函数的调用" << endl;
}
int m_Age;
};
void doWork1(Person0 p)
{
}
Person0 doWork2()
{
Person0 p1;
cout <<"p1的地址"<< (int*)& p1 << endl;
return p1; //首先会根据p1拷贝一个数据,由于p1是当前函数生成的变量(局部变量)执行完会释放p1,所有会执行析构函数
}
void test()
{
//1.使用一个已经创建完毕的对象来初始化一个新对象
cout << "--------1.使用一个已经创建完毕的对象来初始化一个新对象-------" << endl;
Person0 p1(20);
cout << "p1.m_Age:" << p1.m_Age << endl;
Person0 p2(p1);
cout << "p2.m_Age:" << p2.m_Age << endl;
//2.值传递的方式给函数参数传值
cout << "-----------------2.值传递的方式给函数参数传值----------------" << endl;
doWork1(p1);
//3.值方式返回局部对象
cout << "------------------3.值方式返回局部对象-----------------------" << endl;
Person0 p3 = doWork2();
cout << "p3的地址" << (int*)& p3 << endl;
cout << "------------------------4.析构函数---------------------------" << endl;
}
总结:
拷贝构造函数调用时机
1.使用一个已经创建完毕的对象来初始化一个新对象
2.值传递的方式给函数参数传值
3.值方式返回局部对象
3.构造函数的调用规则
//1.创建一个类,c++编译器会给每个类都添加至少3个函数
//默认构造 (空实现)
//析构函数(空实现)
//拷贝构造 (值拷贝)
class Person10
{
public:
//构造函数
Person10()
{
cout << "Person0默认构造函数的调用" << endl;
}
Person10(int age)
{
m_Age = age;
cout << "Person0有参构造函数的调用" << endl;
}
Person10(const Person10& p)
{
m_Age = p.m_Age;
cout << "Person0拷贝构造函数的调用" << endl;
}
//析构函数
~Person10()
{
cout << "Person10析构函数的调用" << endl;
}
int m_Age;
};
void test15()
{
Person10 p1;
p1.m_Age = 18;
cout << "p1.m_Age:" << p1.m_Age << endl;
Person10 p2(p1);
cout << "p2.m_Age:" << p2.m_Age << endl;
}
//2.如果写了有参构造,编译器将不再提供默认构造,但依然提供拷贝构造
class Person10_
{
public:
构造函数
//Person10_()
//{
// cout << "Person10_默认构造函数的调用" << endl;
//}
Person10_(int age)
{
m_Age = age;
cout << "Person10_有参构造函数的调用" << endl;
}
//析构函数
~Person10_()
{
cout << "Person10_析构函数的调用" << endl;
}
int m_Age;
};
void test15_()
{
//Person10_ ; //错误,已有有参构造,不在调用默认构造
Person10_ p1(10);
cout << "p1.m_Age:" << p1.m_Age << endl;
Person10_ p2(p1);
cout << "p2.m_Age:" << p2.m_Age << endl;
}
//3.如果写了拷贝构造,编译器将不再提供其他的构造函数
class Person10_1
{
public:
Person10_1(const Person10_1& p)
{
m_Age = p.m_Age;
cout << "Person0拷贝构造函数的调用" << endl;
}
//析构函数
~Person10_1()
{
cout << "Person10_析构函数的调用" << endl;
}
int m_Age;
};
void test15_()
{
//Person10_1 p ; //错误,已提供拷贝构造,不在调用默认构造
}
总结:
构造函数的调用规则
默认情况下,c++编译器至少给一个类添加3个函数
①.默认构造函数(无参,函数为空)
②.默认析构函数(无参,函数为空)
③.默认拷贝构造函数,对属性进行值拷贝构造函数的调用规则如下:
1.创建一个类,c++编译器会给每个类都添加至少3个函数
2.如果用户定义有参构造函数,C++不在提供默认无参构造,但是会提供默认拷贝构造
3.如果用户定义拷贝构造函数,C++不会提供其他构造函数
4.深拷贝与浅拷贝
class Person11
{
public:
//构造函数
Person11()
{
cout << "Person11默认构造函数的调用" << endl;
}
//有参构造函数
Person11(int age,int height)
{
m_Age = age;
m_Height = new int(height);//new一个数据在堆区,在堆区开辟的数据需要手动释放
cout << "Person11有参构造函数的调用" << endl;
}
//自己实现拷贝构造函数 解决浅拷贝带来的问题
Person11(const Person11& p)
{
cout << "Person11的拷贝构造函数调用" << endl;
m_Age = p.m_Age;
//m_Height = p.m_Height; 编译器默认实现的就是这行代码
//深拷贝操作
m_Height = new int(*p.m_Height);
}
//析构函数
~Person11()
{
//析构代码,将堆区数据释放干净
if (m_Height != NULL)
{
delete m_Height;
m_Height = NULL;//防止野指针出现
}
cout << "Person11析构函数的调用" << endl;
}
int m_Age;
int* m_Height;
};
void test16()
{
Person11 p1(10,180);
cout << "p1.m_Age:" << p1.m_Age << " p1.m_Height:"<< *p1.m_Height << endl;
Person11 p2(p1); //编译默认提供的拷贝构造函数为浅拷贝操作(数据拷贝但是地址没变,会造成堆区的内存重复释放,利用深拷贝代替浅拷贝)
cout << "p2.m_Age:" << p2.m_Age << " p2.m_Height:" << *p2.m_Height << endl;
}
总结:
1.浅拷贝 简单的赋值拷贝操作
2.深拷贝 在堆区申请一个内存编译默认提供的拷贝构造函数为浅拷贝操作(数据拷贝但是地址没变,会造成堆区的内存重复释放,利用深拷贝代替浅拷贝),如果属性有数据在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来问题。
C++初始化列表两种方式
class ClassP {
public:
//传统的方式
ClassP(int a, int b, int c)
{
m_A = a;
m_B = b;
m_C = c;
}
//初始化列表方式
ClassP(int a, int b, int c):m_A(a),m_B(b),m_C(c)
{
}
int m_A;
int m_B;
int m_C;
};
类对象作为类成员
构造函数先构造类对象,然后构造自身,析构则是先析构自身,在析构类对象
//类对象作为类成员
//手机类
class Phone
{
public:
Phone(string pName)
{
m_PName = pName;
cout << "Phone的构造" << endl;
}
~Phone()
{
cout << "Phone的析构" << endl;
}
string m_PName;
};
//人类
class Person
{
public:
// m_Phone(pName) 等同于 Phone m_Phone = pName;//隐式转化法
Person(string name,string pName):m_Name(name),m_Phone(pName)
{
cout << "Person的构造" << endl;
}
~Person()
{
cout << "Person的析构" << endl;
}
//姓名
string m_Name;
//手机号码
Phone m_Phone;
};
总结:
当类B做另外一个类A的成员时候,会先执行这个成员类B对象的构造函数,再执行本身类A的构造函数。
静态成员
访问方式都有两种:
1、通过对象访问
2、通过类名访问(非静态只能同通过对象访问)
1.静态成员变量
//静态成员
class Person_
{
public:
//1、该类所有对象都共享同一份数据
//2、编译阶段就分配内存
//3、类内声明,类外初始化(必须要类外初始化)
static int m_A;
private:
//在私有成员声明的静态成员类外无法访问
//static int m_B;
};
//类外初始化
int Person_::m_A = 0;
void test01()
{
Person_ p1;
cout << p1.m_A << endl;
Person_ p2;
p2.m_A = 200;
cout << p1.m_A << endl;
//当对象p2修改了m_A的值对象p1的m_A也为200;
//说明(静态成员变量不属于某个对象上,所有对象都共享同一份数据),因此访问静态成员变量有两种访问方式
//1、通过对象访问
Person_ p;
cout << p.m_A << endl;
//2、通过类名访问(非静态只能同通过对象访问)
cout << Person_::m_A << endl;
}
总结
1、该类所有对象都共享同一份数据
2、编译阶段就分配内存
3、类内声明,类外初始化(必须要类外初始化)4、在私有成员声明的静态成员类外无法访问
2.静态成员函数
//静态成员函数
//所有对象共享同一个函数
//静态成员函数只能访问静态成员变量
class Person1_
{
public:
static void fun1()
{
cout << "静态函数的访问" << endl;
m_A = 100;//静态成员函数可以访问静态成员变量
//m_B = 200;//静态成员函数无法访问非静态成员变量(无法区分是哪个对象的成员变量m_B)
}
//静态成员变量
static int m_A;
//非静态成员变量
int m_B;
private:
//静态成员函数也有访问权限
static void fun2()
{
cout << "私有静态函数的访问" << endl;
}
};
int Person1_::m_A = 0;
void test02()
{
//1、通过对象访问
Person1_ p;
cout << p.m_A << endl;
p.fun1();
cout << p.m_A << endl;
//2、通过类名访问(非静态只能同通过对象访问)
Person1_::fun1();
//类外无法访问私有静态成员函数
//p.fun2();
//Person1_::fun2();
}
总结
1、静态成员函数
2、所有对象共享同一个函数
3、静态成员函数只能访问静态成员变量4、静态成员函数无法访问非静态成员变量
5、静态成员函数也有访问权限(类外无法访问私有成员的静态函数)
成员变量和成员函数是分开存储的
//空对象
class Person2
{
};
class Person2_
{
int m_A;//非静态成员变量属于类对象上
static int m_B;//静态成员变量 不属于类对象上
void func() {};//非静态成员函数 也不属于类对象上
static void func1() {};//静态成员函数 也不属于类对象上
};
void test03()
{
Person2 p;
//空对象占用空间为:1
//C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置
//每个空对象也需要占一个内存地址
cout << "sizeof p:" << sizeof(p) << endl;
Person2_ p_;
cout << "sizeof p_:" << sizeof(p_) << endl;
}
总结:
空对象占1个字节
非空对象占4个字节,由于只有成员变量属于类上,所以Person2_对象p_占的字节就是成员变量m_A的大小4字节。
this指针
//this指针
class Preson3
{
public:
Preson3(int age)
{
age = age;
}
int age;
};
class Preson3_
{
public:
Preson3_(int age)
{
//this指针指向 被调用的成员函数所属的对象(当形参与成员变量同名,可以用this区分)
this->age = age;
}
void PresonAddAge(Preson3_ &p)
{
this->age += p.age;
}
//值返回
Preson3_ PresonAddAge1_(Preson3_& p) //Preson3_ 值方式返回的是 根据当前对象(当前数据)调用拷贝构造函数复制一个新的对象(新的数据)
{
this->age += p.age;
//this指向调用该函数(PresonAddAge函数)的对象,而*this指向的就是这个对象本身
return *this;
}
//引用返回
Preson3_& PresonAddAge2_(Preson3_ &p) //Preson3_& 返回当前对象本身要用&(引用返回不会拷贝构造复制新的对象)
{
this->age += p.age;
//this指向调用该函数(PresonAddAge函数)的对象,而*this指向的就是这个对象本身
return *this;
}
int age;
};
//1.解决名称冲突
void test04()
{
Preson3 p(18);
cout << p.age << endl;
Preson3_ p1(18);
cout << p1.age << endl;
}
//返回对象本身用*this
void test05()
{
Preson3_ p1(10);
Preson3_ p2(10);
p2.PresonAddAge(p1);
cout << "p2的age为:" << p2.age << endl;
//链式编程思想
//值返回
p2.PresonAddAge1_(p1).PresonAddAge1_(p1).PresonAddAge1_(p1);
cout << "p2的值返回age为:" << p2.age << endl;
//引用返回
p2.PresonAddAge2_(p1).PresonAddAge2_(p1).PresonAddAge2_(p1);
cout << "p2的引用返回age为:" << p2.age << endl;
}
总结:
this指针返回当前创建的对象本身
用途:
1.this指针指向 被调用的成员函数所属的对象(当形参与成员变量同名,可以用this区分)
2.this指向调用该函数(PresonAddAge函数)的对象,而*this指向的就是这个对象本身
注意:
①值方式返回的是 根据当前对象(当前数据)调用拷贝构造函数复制一个新的对象(新的数据)
②返回当前对象本身要用&(引用返回不会拷贝构造复制新的对象)
空指针访问
成员函数
//空指针访问成员函数
class Preson4
{
public:
void showClassName()
{
cout << "this is Person class" << endl;
}
void showPersonAge()
{
//加判断条件
if (this == NULL)
{
return;
}
cout << "age = " << m_Age << endl; //属性前面默认加了一个this (this->m_Age)
}
int m_Age;
};
void test06()
{
Preson4* p = NULL;
p->showClassName();
p->showPersonAge(); //报错,原因是传入的指针(既是当前指针this)为NULL在showPersonAge()函数访问了数据m_Age;
}
总结:
空指针可以访问成员函数
但是需要注意是否用到this指针,如果有用到this指针需要加入判断条件保证代码健壮 const
const 修饰成员函数
class Person5
{
public:
//this指针的本质 是指针常量 指针指向不可以修改
// Person5 * const this; 等下于 this(既是指针指向不可以修改)
//常函数(函数后面加const)
void showPerson() const //等价于 cont Person5 * const this;(在成员函数加const,修饰的是this指向,让指针指向的值也不可以修改)
{
//m_A = 100; //等于 this->m_A = 100;(this可以省略) //报错,因为加了函数后面const说明this指向的值也不能修改
// this = NULL; // 错误,this指针的指向不可以修改
m_B = 100;//加关键字mutable
}
void funC()
{
m_A = 100;
}
int m_A;
mutable int m_B;//特殊变量,既是在常函数里面也可以修改这个值
};
void test05()
{
//常对象
const Person5 p;//在对象前面加const,变为常对象
//p.m_A = 100;//错误,常对象无法访问普通成员变量
p.m_B = 300;//加了关键字mutable 在常对象也可以修改
cout << "p.m_B:" << p.m_B << endl;
//常对象只能调用常函数和常变量
//p.funC(); //错误,无法访问普通函数
}
总结:
常函数:
在成员函数后面加const
常函数不可以修改成员属性
成员属性声明加上关键字mutable后常函数可以修改
常对象:
声明对象前加const称为常对象
常对象只能调用常函数
友元函数
①.全局函数做友元
class Building
{
//友元函数
friend void GoodGay(Building* building); //GoodGay函数能访问Building类的私有成员
public:
Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
string m_SittingRoom;
private:
string m_BedRoom;
};
void GoodGay(Building * building)
{
cout << "正在访问:" << building->m_SittingRoom << endl;
cout << "正在访问:" << building->m_SittingRoom << endl;
}
②.类做友元
//类声明
class Building1;
//类做友元
class GoodGay1
{
public:
GoodGay1();
void visit();//参观函数 访问Buliding中的属性
private:
Building1* building1;
};
class Building1
{
//GoodGay是本类(Building1类)友元函数,可以访问本类(Building1类)私有成员
friend class GoodGay1;
public:
Building1();
string m_SittingRoom;
private:
string m_BedRoom;
};
//类外写成员函数
Building1::Building1()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
GoodGay1::GoodGay1()
{
//创建一个建筑物对象
building1 = new Building1();
}
void GoodGay1::visit()
{
cout << "GoodGay1正在访问:" << building1->m_SittingRoom << endl;
cout << "GoodGay1正在访问:" << building1->m_BedRoom << endl;
}
void test07()
{
GoodGay1 gg;
gg.visit();
}
③.成员函数做友元
//成员函数做友元
class Building2;
class GoodGay2
{
public:
GoodGay2();
void visit1();//让visit1函数可以访问Building中私有成员
void visit2();//让visit2函数不能访问Building中私有成员
Building2* building2;
};
class Building2
{
//告诉编译器visit1是Building2的友元成员可以访问Building2私有成员
friend void GoodGay2::visit1();
//friend void GoodGay2::visit2();
public:
Building2();
string m_SittingRoom;//客厅
private:
string m_BedRoom;//卧室
};
//类外实现成员函数(Building2构造函数)
Building2::Building2()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
GoodGay2::GoodGay2()
{
building2 = new Building2();
}
void GoodGay2::visit1()
{
cout << "visit1正在访问:" << building2->m_SittingRoom << endl;
cout << "visit1正在访问:" << building2->m_BedRoom << endl;
}
void GoodGay2::visit2()
{
cout << "visit2正在访问:" << building2->m_SittingRoom << endl;
//cout << "visit2正在访问:" << building2->m_BedRoom << endl;
}
void test08()
{
GoodGay2 gg;
gg.visit1();
gg.visit2();
}
总结:
友元关键字friend,在本类里面声明其他函数和类为本类的友元成员,可以访问本类的私有成员
运算符重载
1. 加号运算符重载
class Person6
{
public:
//成员函数重载加号
Person6 operator+(Person6& p)
{
Person6 temp;
temp.m_A = m_A + p.m_A;
temp.m_B = m_B + p.m_B;
return temp;
}
int m_A;
int m_B;
};
void test09()
{
Person6 p1;
p1.m_A = 10;
p1.m_B = 10;
Person6 p2;
p2.m_A = 10;
p2.m_B = 10;
//成员函数本质调用
Person6 p3 = p1.operator+(p2);//简化用法为Person6 p3 = p1 + p2;
cout << "p3.m_A:" << p3.m_A << endl;
cout << "p3.m_B:" << p3.m_B << endl;
}
class Person7
{
public:
int m_A;
int m_B;
};
//全局函数重载加号
Person7 operator+(Person7 &p1,Person7 &p2)
{
Person7 p;
p.m_A = p1.m_A + p2.m_A;
p.m_B = p1.m_B + p2.m_B;
return p;
}
//函数重载的版本
Person7 operator+(Person7& p1, int num)
{
Person7 p;
p.m_A = p1.m_A + num;
p.m_B = p1.m_B + num;
return p;
}
void test10()
{
Person7 p1;
p1.m_A = 10;
p1.m_B = 10;
Person7 p2;
p2.m_A = 10;
p2.m_B = 10;
//全局函数本质调用
Person7 p3 = operator + (p1,p2);//简化用法为Person7 p3 = p1 + p2;
cout << "p3.m_A:" << p3.m_A << endl;
cout << "p3.m_B:" << p3.m_B << endl;
//全局函数本质调用(重载版本)
Person7 p4 = operator + (p1, 100);//简化用法为Person7 p4 = p1 + 100;
cout << "p4.m_A:" << p4.m_A << endl;
cout << "p4.m_B:" << p4.m_B << endl;
}
总结:
1.对应内置的数据类型的表达式的运算符是不可能改变的(比如1+1=2不能重载成1+1=0)
2.不要滥用运算符重载 (两个相加的数不能写成相减)
2.左移运算符
//2.左移运算符重载
class Person8
{
//友元函数
friend ostream& operator<<(ostream& cout, Person8& p);
public:
Person8(int a, int b) :m_A(a), m_B(b)
{
}
//利用成员函数重载 左移运算符 p.operator<<(cout) 简化版本 p << cout
//不会利用成员函数重载<<运算符,因为无法实现cout在左侧
/*void operator<<(Person8 &p)
{
}*/
private:
int m_A;
int m_B;
};
//只能利用全局函数重载左移运算符
ostream &operator<<(ostream &cout,Person8 &p)//本质 operator<<(cout,p)简化cout << p;
{
cout << "m_A:" << p.m_A << "m_B:" << p.m_B;
return cout;
}
void test11()
{
Person8 p(10,10);
cout << p << "hello world" << endl;
}
总结:
全局函数可以重载左移运算符
成员函数不能重载左移运算符
重载左移运算符配合友元可以实现输出自定义数据
3.++运算符重载
//重载递增运算符
//自定义整型
class MyIntteger
{
friend ostream& operator<<(ostream& out, MyIntteger myint);
public:
MyIntteger()
{
m_Num = 10;
}
//重载前置++运算符
MyIntteger &operator++() //返回引用是为一直对一个数据进行操作(返回值会复制一个新的数据)
{
//先进行++运算
m_Num = m_Num + 1;
//返回自身
return *this;
}
//重载后置++运算符
MyIntteger operator++(int)//加int代表占位参数(进行函数重载(只能是int)),为了区分前置递增和后置递增
{
//先记录当前结果
MyIntteger temp = *this;
//后递增
m_Num++;
//后返回记录的结果
return temp;
}
private:
int m_Num;
};
//重载左移运算符
ostream& operator<<(ostream& out, MyIntteger myint)
{
out << "m_Num:" << myint.m_Num ;
return out;
}
void test12()
{
//前置递增
MyIntteger myint;
cout << ++myint << endl;
cout << myint << endl;
}
void test13()
{
//后置递增
MyIntteger myint1;
cout << myint1++ << endl;
cout << myint1 << endl;
}
总结:
前置递增是先进行自加计算再返回(前置递增返回引用)
后置递增是先返回自身在进行自加(后置递增返回值并且需要加一个占位参数)
4.赋值运算符重载
//赋值运算符重载
class Person9
{
public:
Person9(int age)
{
m_Age = new int(age);
}
~Person9()
{
//手动开辟的内存需要手动释放
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
}
//重载 赋值运算符
Person9& operator= (Person9 &p)
{
//编译器是提供浅拷贝
//m_Age = p.m_Age;
//应该先判断是否属性在堆区,如果有先释放干净,然后在深拷贝
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
//深拷贝
m_Age = new int(*p.m_Age);
return *this;
}
int *m_Age;
};
void test14()
{
Person9 p1(18);
cout << *p1.m_Age << endl;
Person9 p2(20);
cout << *p2.m_Age << endl;
Person9 p3(22);
cout << *p3.m_Age << endl;
p3 = p2 = p1;//赋值操作
cout << *p1.m_Age << endl;
cout << *p2.m_Age << endl;
cout << *p3.m_Age << endl;
}
总结:
编译器提供的赋值运算符重载是浅拷贝操作,如果创建的数据在堆区时候并在析构函数进行释放时候会进行堆区数据的重复释放。
5.关系运算符重载
class Person12
{
public:
Person12(string name, int age)
{
m_Name = name;
m_Age = age;
}
//重载 == 号
bool operator==(Person12 &p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) //this->m_Name == p.m_Name 和 strcmp(this->m_Name.c_str(), p.m_Name.c_str()) == 0 都能判断string相等
{
return true;
}
return false;
}
//重载 != 号
bool operator!=(Person12& p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
{
return false;
}
return true;
}
string m_Name;
int m_Age;
};
void test17()
{
Person12 p1("Tom", 18);
Person12 p2("Tom", 18);
//==号的重载
if (p1 == p2)
{
cout<< "p1和p2是相等的" <
总结:
重载关系运算符,可以让两个自定义的类型对象进行对比操作
6.函数调用运算符重载
//运算符重载打印输出类
class MyPrint
{
public:
//重载函数运算符
void operator()(string test)
{
cout << test << endl;
}
};
//函数打印
void MyPrint01(string test)
{
cout << test << endl;
}
//运算符重载加法类
class MyAdd
{
public:
int operator()(int num1, int num2)
{
return num1 + num2;
}
};
void test18()
{
//函数重载打印类
MyPrint myPrint;
myPrint("hello word");//由于使用起来非常像函数调用,所以称之为仿函数
//函数调用打印类
MyPrint01("hello word");
MyAdd myadd;
int ret = myadd(10, 20);
cout << "ret:" << ret << endl;
//匿名对象
cout << MyAdd()(100, 100) << endl;
}
总结:
1.函数调用运算符()也可以重载
2.由于重载使用的方式非常像函数调用,因此称为仿函数
3.仿函数没有固定写法,非常灵活
继承
1.继承基本语法
//普通方式实现
//Java页面
class Java
{
public:
void header()
{
cout << "首页、公开课、登录、注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、C++...(公共分类列表)" << endl;
}
void content()
{
cout << "Java学科视频" << endl;
}
};
//Python页面
class Python
{
public:
void header()
{
cout << "首页、公开课、登录、注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、C++...(公共分类列表)" << endl;
}
void content()
{
cout << "Python学科视频" << endl;
}
};
//C++页面
class Cpp
{
public:
void header()
{
cout << "首页、公开课、登录、注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、C++...(公共分类列表)" << endl;
}
void content()
{
cout << "C++学科视频" << endl;
}
};
void test19()
{
//普通方式
cout << "-------------普通方式实现----------------" << endl;
cout << "------Java下载视频页面如下:-----" << endl;
Java ja;
ja.header();
ja.footer();
ja.left();
ja.content();
cout << "-----Python下载视频页面如下:------" << endl;
Python py;
py.header();
py.footer();
py.left();
py.content();
cout << "-----C++下载视频页面如下:------" << endl;
Cpp cpp;
cpp.header();
cpp.footer();
cpp.left();
cpp.content();
}
//继承方式实现
class BasePage
{
public:
void header()
{
cout << "首页、公开课、登录、注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、C++...(公共分类列表)" << endl;
}
};
//Java页面
class Java1 : public BasePage
{
public:
void content()
{
cout << "Java学科视频" << endl;
}
};
//Java页面
class Python1 : public BasePage
{
public:
void content()
{
cout << "Python学科视频" << endl;
}
};
//Java页面
class Cpp1 : public BasePage
{
public:
void content()
{
cout << "C++学科视频" << endl;
}
};
void test20()
{
//继承方式
cout << endl;
cout << "-------------继承方式实现----------------" << endl;
cout << "Java下载视频页面如下:" << endl;
Java1 ja;
ja.header();
ja.footer();
ja.left();
ja.content();
cout << "-----Python下载视频页面如下:-----" << endl;
Python1 py;
py.header();
py.footer();
py.left();
py.content();
cout << "------C++下载视频页面如下------:" << endl;
Cpp1 cpp;
cpp.header();
cpp.footer();
cpp.left();
cpp.content();
}
总结:
①继承好处:减少重复代码
②基本语法: class 子类 :继承方式 父类
③例如 公有继承:class A :public B; A(子类)也称为派生类,B(父类)也称为基类
其中(子类)派生类中的成员,包含两大部分:
一类是从基类继承过来的,一类是自己增加的成员
从基类继承过来的表现其共性,而新增的成员表现其个性。
2.继承的三种方式
//继承方式有三种
//公有继承
class Base1
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son1 : public Base1
{
public:
void func()
{
m_A = 10; //父类中的公共权限成员 到子类中依然是公共权限
m_B = 10; //父类中的保护权限成员 到子类中依然是保护权限
//m_C = 10; //父类中的私有权限成员 子类访问不到
}
};
void test21()
{
Son1 s1;
s1.m_A;
//s1.m_B; // 在Son1中m_B是保护权限,类外不能访问
}
//保护继承
class Base2
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son2 : protected Base2
{
public:
void func()
{
m_A = 10; //父类中的公共权限成员 到子类中变为保护权限
m_B = 10; //父类中的保护权限成员 到子类中依然是保护权限
//m_C = 10; //父类中的私有权限成员 子类访问不到
}
};
void test22()
{
Son2 s2;
//s2.m_A;// 在Son2中m_A是保护权限,类外不能访问
//s1.m_B; // 在Son1中m_B是保护权限,类外也不能访问
}
//私有继承
class Base3
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son3 : private Base3
{
public:
void func()
{
m_A = 10; //父类中的公共权限成员 到子类中变为有权限
m_B = 10; //父类中的保护权限成员 到子类中变为私有权限
//m_C = 10; //父类中的私有权限成员 子类访问不到
}
};
class GrandSon3 :public Son3
{
public:
void fun()
{
//m_A = 1000; //到了Son3中的m_A变为私有,在GrandSon3中不能访问
//m_B = 1000; //到了Son3中的m_B变为私有,在GrandSon3中不能访问
}
};
void test22()
{
Son3 s3;
//s3.m_A; // 在Son3中m_A是私有权限,类外不能访问
//s3.m_B; // 在Son3中m_B是私有权限,类外也不能访问
}
总结:
继承三种方式:
①公共继承(public)
②保护继承(protected)
③私有继承(private)
1.公有继承:父类中的公有成员(到子类仍是公有成员)和保护成员(到子类仍然是保护成员)子类可以访问,私有成员不能访问,(子类在类外创建对象时候,父类的公有成员可以访问,保护和私有成员不能访问)
2.保护继承:父类中的公有成员(到子类变为保护成员)和保护成员(到子类仍然是保护成员)子类可以访问,私有成员不能访问,(子类在类外创建对象时候,父类的公有成员、保护和私有成员都不能访问)
3.私有继承:父类中的公有成员(到子类变为私有成员)和保护成员(到子类变为私有成员)子类可以访问,私有成员不能访问,(子类在类外创建对象时候,父类的公有成员、保护和私有成员都不能访问)
3.继承中的对象模型
class Base
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son : public Base
{
public:
int m_D;
};
void test24()
{
cout << "size of Son = " << sizeof(Son) << endl;
//sizeof(Son)为16 说明父类中所有的属性(非静态的属性)子类都会继承下来,父类私有成员被编译器隐藏访问不到但是能继承
}
总结:
父类中所有的属性(非静态的属性)子类都会继承下来,父类私有成员被编译器隐藏访问不到但是能继承。
4.继承中的构造和析构顺序
class Base4
{
public:
Base4()
{
cout << "Base4的构造函数" << endl;
}
~Base4()
{
cout << "Base4的析构函数" << endl;
}
};
class Son4 : public Base4
{
public:
Son4()
{
cout << "Son4的构造函数" << endl;
}
~Son4()
{
cout << "Son4的析构函数" << endl;
}
};
void test25()
{
Base4 b;
//继承中的构造和析构顺序如下:
//先构造父类,再构造子类,析构顺序与构造顺序相反
Son4 s;
}
总结:
继承中的构造和析构顺序如下:
先构造父类,再构造子类(先父后子),析构顺序与构造顺序相反
5.继承中同名成员的处理方式
//继承中同名的成员处理方式
class Base5
{
public:
Base5()
{
m_A = 100;
}
void func()
{
cout << "Base5的func函数调用" << endl;
}
void func(int a)
{
cout << "Son5的func的(int a)调用" << endl;
}
int m_A;
};
class Son5 : public Base5
{
public:
Son5()
{
m_A = 200;
}
void func()
{
cout << "Son5的func函数调用" << endl;
}
int m_A;
};
//同名成员属性
void test26()
{
Son5 s;
cout << "Son5 的m_A = " << s.m_A << endl;
//如果通过子类访问父类的同名成员,需要加作用域
cout << "Son5 的m_A = " << s.Base5::m_A << endl;
}
//同名成员函数
void test27()
{
Son5 s;
s.func();//直接调用 调用的是子类中的同名成员
//如果通过子类访问父类的同名成员,需要加作用域
s.Base5::func();
s.Base5::func(100);
//如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有的同名成员函数
//cout << "Son5 的func = " << s.Base5::func << endl;
}
总结:
1.子类对象可以1直接访问到子类同名成员
2.子类对象作用域可以访问到父类同名成员
3.当子类与父类永远同名的成员函数,子类会隐藏父类中的同名函数,加作用域可以访问到父类中同名函数
6.继承同名静态成员的访问方式
//继承同名静态成员处理方式
class Base6
{
public:
static int m_A;
static void func()
{
cout << "Base6的func()的调用" << endl;
}
static void func(int a)
{
cout << "Base6 的func(int a)的调用" << endl;
}
};
int Base6::m_A = 100;
class Son6 : public Base6
{
public:
static int m_A;
static void func()
{
cout << "Son6 的func()的调用" << endl;
}
static void func(int a)
{
cout << "Son6 的func(int a)的调用" << endl;
}
};
int Son6::m_A = 200;
//同名静态成员属性
void test28()
{
//1.通过对象访问数据
Son6 s;
cout << "通过对象访问m_A-------------" << endl;
cout << "Son6下的m_A = " << s.m_A << endl;
cout << "Base下的m_A = " << s.Base6::m_A << endl;
//通过类名访问
cout << "通过类名访问m_A-------------" << endl;
cout << "Son6下的m_A = " << Son6::m_A << endl;
//第一个::代表通过类名访问方式,第二个::代表访问父类作用域下的
cout << "Base下的m_A = " << Son6::Base6::m_A << endl;
}
//同名静态成员函数
void test29()
{
//通过对象访问
cout << "通过对象访问func()-------------" << endl;
Son6 s;
s.func();
s.Base6::func();
cout << "通过对象访问func(int a)-------------" << endl;
s.func(100);
s.Base6::func(100);
//通过类名访问
cout << "通过类名访问func()-------------" << endl;
Son6::func();
Son6::Base6::func();
cout << "通过类名访问func(int a)-------------" << endl;
Son6::func(100);
Son6::Base6::func(100);
}
总结:
同名静态成员的处理方式和非静态的处理方式一样,区别在于静态的访问可以通过对象和类名两种方式访问
7.多继承语法
//多继承语法
class Base7
{
public:
Base7()
{
m_A = 100;
}
int m_A;
};
class Base8
{
public:
Base8()
{
m_B = 200;
m_A = 200;
}
int m_A;
int m_B;
};
//子类 需要继承Base1和Base2
class Son7 : public Base7, public Base8
{
public:
Son7()
{
m_C = 300;
m_D = 400;
}
int m_C;
int m_D;
};
void test30()
{
Son7 s;
cout << "sizef of = " << sizeof(s) << endl;
//两个父类具有同名的成员
cout << " Base7下的m_A = " <
总结:
语法:class 子类 :public 父类1 ,public 父类2......
多继承中如果父类中出现了同名情况,子类使用时候要加作用域。
8.菱形继承
//利用虚继承可以解决菱形继承的问题
//继承之前加上关键字 virtual 变成虚继承(基类称为虚基类)
//羊类
class Sheep : virtual public Animal
{
public:
};
//驼类
class Tou :virtual public Animal
{
public:
};
//羊驼类
class SheepTou : public Sheep,public Tou
{
};
void test31()
{
SheepTou st;
st.Sheep::m_Age = 18;
st.Tou::m_Age = 20;
//当菱形继承,有两个父类拥有相同的数据,需要加作用域区分
cout << "Sheep::m_Age = " << st.Sheep::m_Age << endl;
cout << "Tou::m_Age = " << st.Tou::m_Age << endl;
//这份数据只要有一份就可以,菱形继承导致数据有两份,浪费资源(利用菱形继承可以解决问题,数据变成一份)
cout << "st.m_Age = " << st.m_Age << endl;
}
总结:
菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义
利用虚继承可以解决菱形继承的问题
多态
1.多态的基本概念
//动物类
class Anaimal1
{
public:
//虚函数
virtual void speak()
{
cout << "动物在说话" << endl;
}
};
//狗类
class Dog : public Anaimal1
{
public:
//子类重写父类的虚函数
void speak()
{
cout<<"小狗在说话"<
总结:
动态多态满足条件
1.有继承关系
2.子类重写父类虚函数(重载是函数名相同参数不同,重写是是函数名、返回值类型和参数列表完全相同)
动态多态的使用
父类的指针或引用 执行子类对象地址早绑定 在编译阶段确定函数地址
如果向执行让传入的对象函数,那么这个函数地址不能提前绑定(虚函数实现),需要在运行阶段进行绑定,地址晚绑定。
2.多态案例一-计算器
//多态案例-计算器类
//普通写法
class Calculator
{
public:
int getResult(string oper)
{
if (oper == "+")
{
return m_Num1 + m_Num2;
}
else if(oper == "-")
{
return m_Num1 - m_Num2;
}
else if (oper == "*")
{
return m_Num1 * m_Num2;
}
//如果需要扩展新的功能,需要修改源码
//在开发中,提倡开闭原则:对扩展进行开发,对修改进行关闭
}
int m_Num1;
int m_Num2;
};
void test02()
{
//创建计算器的对象
Calculator c;
c.m_Num1 = 10;
c.m_Num2 = 20;
cout << c.m_Num1 << "+" << c.m_Num2 << "=" << c.getResult("+")<< endl;
cout << c.m_Num1 << "-" << c.m_Num2 << "=" << c.getResult("-") << endl;
cout << c.m_Num1 << "*" << c.m_Num2 << "=" << c.getResult("*") << endl;
}
//利用多态实现计算器
//实现计算器抽象类
class AbstractCalculator
{
public:
virtual int getResult()
{
return 0;
}
int m_Num1;
int m_Num2;
};
//加法计算器类
class AddCalculator : public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 + m_Num2;
}
};
//减法计算器类
class SubCalculator : public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 - m_Num2;
}
};
//乘法计算器类
class MulCalculator : public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 * m_Num2;
}
};
void test02_()
{
//多态使用条件:父类的指针或引用指向子类对象
//加法运算
AbstractCalculator* abc = new AddCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << "+" << abc->m_Num2 << "=" << abc->getResult() << endl;
//在堆区创建指针对象用完记得销毁(释放的是堆区的数据,但是指针类型还是父类的)
delete abc;
//减法运算
abc = new SubCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << "-" << abc->m_Num2 << "=" << abc->getResult() << endl;
//在堆区创建指针对象用完记得销毁
delete abc;
//减法运算
abc = new MulCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << "*" << abc->m_Num2 <<"="<< abc->getResult() << endl;
//在堆区创建指针对象用完记得销毁
delete abc;
}
总结:
多态好处:
1.组织结构清晰
2.可读性强
3.对于前期和后期维护性高
3.纯虚函数和抽象类
//纯虚函数和抽象类
class Base
{
public:
//只要有一个纯虚函数,这个类称为抽象类(无法实例化对象)
virtual void func() = 0;//纯虚函数
};
class Son1 : public Base
{
public:
//父类有纯虚函数子类必须要重写父类的虚函数,否则也是抽象类
virtual void func()
{
cout << "Son1中的func函数的调用" << endl;
}
};
class Son2 : public Base
{
public:
//父类有纯虚函数子类必须要重写父类的虚函数,否则也是抽象类
virtual void func()
{
cout << "Son2中的func函数的调用" << endl;
}
};
void test03()
{
//下面实例化对象错误(无论是栈区还是堆区有纯虚函数的 类都不能实例化对象)
//Base b;
//new Base;
//通过父类对象调用
Base* base = new Son1;
base->func();
delete base;
base = new Son2;
base->func();
delete base;
//通过子类对象调用
Son1* s1 = new Son1;
s1->func();
delete s1;
Son2* s2 = new Son2;
s2->func();
delete s2;
}
总结:
在多态中,通常父类中虚函数的实现是毫无意义的,主要是调用子类重写的内容,因此可以将虚函数改写成纯虚函数
纯虚函数语法:virtual 返回值类型 函数名(参数列表) = 0;(当类中有了纯虚函数,这个类也称为抽象类)
抽象类特点:
1.无法实例化对象
2.子类必须重写抽象类中的纯虚函数,否则也属于抽象类(无法实例化对象)
4.多态案例二-制作饮品
//多态案例2-制作饮品
class AbstractDrinking
{
public:
//煮水
virtual void Boil() = 0;
//冲泡
virtual void Brew() = 0;
//倒入杯中
virtual void PourInCup() = 0;
//加如辅料
virtual void PutSomething() = 0;
//制作饮品
void makeDrink()
{
Boil();
Brew();
PourInCup();
PutSomething();
}
};
//制作咖啡
class Coffer : public AbstractDrinking
{
//重写父类纯虚函数
//煮水
virtual void Boil()
{
cout << "煮山泉水" << endl;
}
//冲泡
virtual void Brew()
{
cout << "冲泡咖啡粉" << endl;
}
//倒入杯中
virtual void PourInCup()
{
cout << "倒入咖啡杯子中" << endl;
}
//加如辅料
virtual void PutSomething()
{
cout << "放少许糖" << endl;
}
};
//制作茶叶
class Tea : public AbstractDrinking
{
//重写父类纯虚函数
//煮水
virtual void Boil()
{
cout << "煮山泉水" << endl;
}
//冲泡
virtual void Brew()
{
cout << "冲泡茶叶" << endl;
}
//倒入杯中
virtual void PourInCup()
{
cout << "倒入茶杯中" << endl;
}
//加如辅料
virtual void PutSomething()
{
cout << "放枸杞" << endl;
}
};
//制作函数
void doWork(AbstractDrinking * abs)
{
abs->makeDrink();
delete abs;//释放内存
}
void test04()
{
//制作咖啡
doWork(new Coffer);
cout << "-------------------------" << endl;
//制作茶叶
doWork(new Tea);
}
总结:
利用同一个父类接口实现不同子类具体函数功能
5.虚析构和纯虚析构
//虚析构
class Anaimal1
{
public:
virtual void speak() = 0;
Anaimal1()
{
cout << "Anaimal1的构造函数" << endl;
}
//利用虚析构可以解决父类不释放子类对象问题
virtual ~Anaimal1()
{
cout << "Anaimal1的虚析构函数" << endl;
}
};
class Cat1 : public Anaimal1
{
public:
Cat1(string name)
{
cout << "Cat1的构造函数调用" << endl;
m_Name = new string(name);
}
virtual void speak()
{
cout << *m_Name << "小猫在说话" << endl;
}
//析构函数释放内存
~Cat1()
{
if (m_Name != NULL)
{
cout << "Cat1的析构函数调用" << endl;
delete m_Name;
m_Name = NULL;
}
}
string* m_Name;
};
//纯虚析构
class Anaimal2
{
public:
virtual void speak() = 0;
Anaimal2()
{
cout << "Anaimal2的构造函数" << endl;
}
//纯虚析构(需要声明也需要实现)纯虚析构也可以解决父类不释放子类对象问题
//有了纯虚析构,这个类也属于抽象类,无法实例化对象
virtual ~Anaimal2() = 0;
};
//纯虚析构
Anaimal2::~Anaimal2()
{
cout << "Anaimal2的纯虚析构函数" << endl;
}
class Rat1 : public Anaimal2
{
public:
Rat1(string name)
{
cout << "Rat1的构造函数调用" << endl;
m_Name = new string(name);
}
virtual void speak()
{
cout << *m_Name << "老鼠在说话" << endl;
}
//析构函数释放内存
~Rat1()
{
if (m_Name != NULL)
{
cout << "Rat1的析构函数调用" << endl;
delete m_Name;
m_Name = NULL;
}
}
string* m_Name;
};
void test05()
{
//利用虚析构解决父类不能释放子类在堆区的数据
//父类的指针指向子类的
Anaimal1* animal1 = new Cat1("汤姆");
animal1->speak();
//父类指针在析构时候 不会调用子类析构函数,导致子类有堆区的数据会出现内存泄露
delete animal1;
cout << "-----------------" << endl;
//利用纯虚析构解决父类不能释放子类在堆区的数据
Anaimal2* animal2 = new Rat1("杰瑞");
animal2->speak();
delete animal2;
}
总结:
多态使用时候,如果子类有属性开辟在堆区,那么父类的指针释放时候无法调用子类的析构函数
解决方法:将父类的析构函数改写为虚析构或者纯虚析构。
1.虚析构和纯虚析构共性:
①.可以解决父类指针释放子类对象
②.都需要有具体的函数实现
2.虚函数和纯虚函数区别
如果是纯虚析构则该类属于抽象类,无法实例化对象
3.虚析构语法:
类内声明和实现:virtual ~类名(){};
4.纯虚析构语法:
类内声明:virtual ~类名() = 0;
类外实现:类名::~类名(){};
5.总结
①虚析构和纯虚析构就是用来解决通过父类指针释放子类对象
②如果子类没有堆区数据,可以不写为虚析构或纯虚析构
⑥拥有纯虚析构函数的类也属于抽象类
6.多态案例三-电脑组装
//多态案例三-电脑组装
//抽象CPU类
class CPU
{
public:
//抽象计算的函数
virtual void calculate() = 0;
};
//抽象显卡类
class VideoCard
{
public:
//抽象显示的函数
virtual void display() = 0;
};
//抽象内存条类
class Memory
{
public:
//抽象存储的函数
virtual void storage() = 0;
};
//电脑类
class Computer
{
public:
Computer(CPU* cpu, VideoCard* vc, Memory* men)
{
m_cpu = cpu;
m_vc = vc;
m_men = men;
}
//提供工作的函数
void work()
{
m_cpu->calculate();
m_vc->display();
m_men->storage();
}
//提供析构函数 释放3个电脑零件
~Computer()
{
if (m_cpu != NULL)
{
delete m_cpu;
}
if (m_vc != NULL)
{
delete m_vc;
}
if (m_men != NULL)
{
delete m_men;
}
}
private:
CPU* m_cpu;//CPU的零件指针
VideoCard* m_vc;//显卡零件指针
Memory* m_men;//内存条零件指针
};
//具体的厂商
//Inter厂商
class InterCPU : public CPU
{
public:
virtual void calculate()
{
cout << "Inter的CPU开始计算了" << endl;
}
};
class InterVideoCard: public VideoCard
{
public:
virtual void display()
{
cout << "Inter的显卡开始显示了" << endl;
}
};
class InterMemory : public Memory
{
public:
virtual void storage()
{
cout << "Inter的内存条开始存储了" << endl;
}
};
//Lenovo厂商
class LenovoCPU : public CPU
{
public:
virtual void calculate()
{
cout << "Lenovo的CPU开始计算了" << endl;
}
};
class LenovoVideoCard : public VideoCard
{
public:
virtual void display()
{
cout << "Lenovo的显卡开始显示了" << endl;
}
};
class LenovoMemory : public Memory
{
public:
virtual void storage()
{
cout << "Lenovo的内存条开始存储了" << endl;
}
};
void test06()
{
cout << "第一台电脑开始工作----------" << endl;
//第一台电脑
CPU* interCup = new InterCPU;
VideoCard* interViedoCard = new InterVideoCard;
Memory* interMemory = new InterMemory;
//创建第一台电脑
Computer* Computer1 = new Computer(interCup, interViedoCard, interMemory);
Computer1->work();
delete Computer1;
cout << "-----------------------------" << endl;
cout << "第二台电脑开始工作----------" << endl;
//创建第二台电脑
Computer* Computer2 = new Computer(new LenovoCPU, new LenovoVideoCard,new LenovoMemory);
Computer2->work();
delete Computer2;
cout << "-----------------------------" << endl;
cout << "第三台电脑开始工作----------" << endl;
//创建第三台电脑
Computer* Computer3 = new Computer(new LenovoCPU, new InterVideoCard, new LenovoMemory);
Computer3->work();
delete Computer3;
}
写文件
/*程序运行时产生的数据都属于临时数据,程序一旦运行结束就会被释放
通过文件可以将数据持久化
C++中对文件操作需要包含头文件文件类型分为两种:
1.文本文件 :文件以文本的ASCLL码形式存储在计算机当中
2.二进制文件:文件以文本的二进制形式存储在计算机当中,用户一般不能直接读懂
*/
1.文本文件
1.1写文件
//头文件
#include
/*操作文件三大类
1.ofstream 写操作
2.ifstream 读文件
3.fstream 读写文件
写文件步骤如下:
1.包含头文件
#include
2.创建流对象
ofstream ofs;
3.打开文件
ofs.open("文件路径",打开方式);
4.写数据
ofs<<"写入的数据"<
总结:
/*操作文件三大类
1.ofstream 写操作
2.ifstream 读文件
3.fstream 读写文件写文件步骤如下:
1.包含头文件
#include
2.创建流对象
ofstream ofs;
3.打开文件
ofs.open("文件路径",打开方式);
4.写数据
ofs<<"写入的数据"<5.关闭文件
ofs.close();
文件打开方式:
打开方式 解释
ios::in 为读文件而打开文件
ios::out 为写文件而打开文件
ios::ate 初始化位置:文件尾
ios::app 追加方式写文件
ios::trunc 如果文件存在先删除,再创建
ios::binary 二进制方式注意:文件打开方式可以配合使用,利用|操作符
例如:用二进制方式写文件ios::binary | ios::out
*/
1.2读文件
//读文件
//第一种方式
#include
void readingData1(char* buf, ifstream& ifs)
{
cout << "--------第一种方式-----------" << endl;
while (ifs >> buf)//一行一行读,读到头就会返回flase退出while循环
{
cout << buf << endl;
}
}
//第二种方式
void readingData2(char *buf, ifstream &ifs,int len)
{
cout << "--------第二种方式-----------" << endl;
while (ifs.getline(buf, len))//一行一行的读取,第二个参数是读取数据的长度
{
cout << buf << endl;
}
}
//第三种方式
void readingData3(string buf,ifstream &ifs)
{
cout << "--------第三种方式-----------" << endl;
while (getline(ifs,buf))//一行一行读取
{
cout << buf << endl;
}
}
//第四种方式
void readingData4(char &c,ifstream &ifs)
{
cout << "--------第四种方式-----------" << endl;
while ((c = ifs.get()) != EOF )//EOF (一个一个字符读效率比较慢)
{
cout << c;
}
}
int test08()
{
//1.包含头文件
//2.创建流对象
ifstream ifs;
//3.打开文件 并且判断是否打开成功
ifs.open("test.txt", ios::in);
if ( !ifs.is_open())
{
cout << "文件打开失败!" << endl;
return 0;
}
//4.读数据
char buf[1024] = { 0 };
/*第一种方式读数据*/
readingData1(buf, ifs);
/*第二种方式读数据*/
//readingData2(buf, ifs,sizeof(buf));
/*第三种方式读数据*/
string buf1;
//readingData3(buf1,ifs);
/*第四种方式读数据*/
char c;
//readingData4(c, ifs);
//5.关闭文件
ifs.close();
}
总结:
第四种方式不太建议用,一个一个字符读效率慢
读文件可以利用ifstream,或者fstream类
利用is_open函数可以判断文件是否打开成功
close关闭文件
2.二进制文件
//二进制文件
/*以二进制方式对文件进行读写操作
打开方式要指定为ios::binary*/
/*写文件
二进制方式写文件主要是利用流对象调用成员函数write
函数原型:ostream &write(const char *buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间,len是读写字节数*/
class Person
{
public:
char m_Name[64];//姓名
int m_Age;//年龄
};
int test09()
{
//1.包含头文件
//2.创建流对象
ofstream ofs; //ofstream ofs("preson.txt", ios::out | ios::binary);
//3.打开文件
ofs.open("person.txt", ios::out | ios::binary);//指定以
//4.写文件
Person p = { "张三",18 };
ofs.write((const char*)&p,sizeof(Person));
//5.关闭文件
ofs.close();
return 0;
}
2.1读文件
class Person
{
public:
char m_Name[64];//姓名
int m_Age;//年龄
};
int test09()
{
//1.包含头文件
//2.创建流对象
ofstream ofs; //ofstream ofs("preson.txt", ios::out | ios::binary);
//3.打开文件
ofs.open("person.txt", ios::out | ios::binary);//指定以
//4.写文件
Person p = { "张三",18 };
ofs.write((const char*)&p,sizeof(Person));
//5.关闭文件
ofs.close();
return 0;
}
总结:
1.文件输出流对象,可以通过wirte函数,以二进制方式写数据
二进制文件
2.以二进制方式对文件进行读写操作
打开方式要指定为ios::binary3.写文件
二进制方式写文件主要是利用流对象调用成员函数write
函数原型:ostream &write(const char *buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间,len是读写字节数
2.2读文件
//二进制的方式读文件主要利用流对象调用成员函数read
/*
函数原型:istream &read(char *buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间,len是读写的字节数
*/
class Person1
{
public:
char m_Name[64];//姓名
int m_Age;//年龄
};
int test10()
{
//1.包含头文件
//2.创建流对象
ifstream ifs; //ofstream ofs("preson.txt", ios::out | ios::binary);
//3.打开文件
ifs.open("person.txt", ios::in | ios::binary);//指定以
if (!ifs.is_open())
{
cout << "打开文件失败" << endl;
}
//4.读文件
cout << "读取二进制文件" << endl;
Person p;
ifs.read((char*)&p, sizeof(Person));
cout << "姓名:" << p.m_Name << endl;
cout << "年龄:" << p.m_Age << endl;
//5.关闭文件
ifs.close();
return 0;
}
总结:
二进制的方式读文件主要利用流对象调用成员函数read
文件输出流对象,可以通过read函数,以二进制方式读取数据
/*
函数原型:istream &read(char *buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间,len是读写的字节数
*/