10.2、对象的初始化和清理
10.2.1、构造函数和析构函数
C++利用构造函数和析构函数实现对象的初始化和清理。
由编译器自动调用,无需手动操作。构造和析构是必须有的,如果自己没有实现,编译器会自动生成一个空实现的构造和析构函数。
构造函数语法:类名(){}
- 构造函数没有返回值也不写void
- 函数名称和类名相同
- 构造函数可以有参数,因此可以发生函数重载
- 程序在调用对象时,自动调用构造函数,无须手动调用,且只会调一次。
析构函数语法:~类名(){}
- 构造函数没有返回值也不写void
- 函数名称和类名相同,在名称前加上~
- 构造函数不可以有参数,因此不可以发生函数重载
- 程序在对象销毁前,自动调用析构函数,无须手动调用,且只会调一次。
#include<iostream>
using namespace std;
class Person
{
public:
//构造函数
Person()
{
cout << "person构造函数的调用" << endl;
}
//析构函数
~Person()
{
cout << "person析构函数的调用" << endl;
}
};
void test01()
{
Person p;
}
int main()
{
test01();
return 0;
}
10.2.2、构造函数的分类和调用
按参数分类:有参构造和无参构造
按类型分类:普通构造和拷贝构造
调用方式分法:括号法、显示法、隐式转换法。
#include<iostream>
using namespace std;
class Person
{
public:
//构造函数
Person()//无参构造
{
cout << "person构造函数的无参调用" << endl;
}
Person(int a)//有参构造
{
age = a;
cout << "person构造函数的有参调用" << endl;
}
//拷贝构造函数
Person(const Person &p)//必须是const person 且以引用传递
{
age = p.age;//将传入的人身上所有属性拷贝
cout << "person构造函数的拷贝调用" << endl;
}
//析构函数
~Person()
{
cout << "person析构函数的调用" << endl;
}
int age;
};
//调用
void test01()
{
//括号法
Person p1;//默认构造函数的调用——不要加小括号
Person p2(10);//有参构造函数调用
Person p3(p2);//拷贝构造函数调用
cout << "p2的年龄:" << p2.age << endl;
cout << "p3的年龄:" << p3.age << endl;
//显示法
Person p4;
Person p5 = Person(10);
Person p6 = Person(p2);
Person(10);//匿名对象,当前行执行结束后,系统立即回收
cout << "aaaaaaa" << endl;
//不要利用拷贝构造函数,初始化匿名对象。
//Person(p2);编译器认为Person (p2)等价于Person p2;
//隐式法
Person p7 = 10;
Person p8 = p7;
}
int main()
{
test01();
return 0;
}
10.2.3、拷贝构造函数调用时机
1、使用一个已经创建完毕的对象来初始化一个新对象
2、值传递的方式给函数参数传值
3、值方式返回局部对象
#include<iostream>
using namespace std;
class Person
{
public:
Person()
{
cout << "person构造函数的无参调用" << endl;
}
~Person()
{
cout << "person析构函数的调用" << endl;
}
Person(int a)//有参构造
{
age = a;
cout << "person构造函数的有参调用" << endl;
}
//拷贝构造函数
Person(const Person& p)//必须是const person 且以引用传递
{
age = p.age;//将传入的人身上所有属性拷贝
cout << "person构造函数的拷贝调用" << endl;
}
int age;
};
//使用一个已经创建完毕的对象来初始化一个新对象
void test01()
{
Person p1(20);
Person p2(p1);
cout << "p2的年龄为:" << p2.age << endl;
}
//值传递的方式给函数参数传值。
void dowork(Person p)
{
}
void test02()
{
Person p;
dowork(p);
}
//值方式返回局部对象
Person dowork1()
{
Person p1;
cout << (int)&p1 << endl;
return p1;
}
void test03()
{
Person p = dowork1();
cout << (int)&p << endl;
}
int main()
{
//test01();
//test02();
test03();
return 0;
}
10.2.4、构造函数调用规则
调用规则:
- 用户定义有参构造函数,C++将不会提供无参构造函数了,但是会提供拷贝构造函数。
- 用户定义拷贝构造函数,C++将不会提供其他构造函数了。
10.2.5、深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作。(编译器直接提供的)——可能导致堆区内存重复释放的错误。
深拷贝:在堆区重新申请空间,进行烤贝操作(new和delete函数)
#include<iostream>
using namespace std;
class Person
{
public:
//构造函数
Person()//无参构造
{
cout << "person构造函数的无参调用" << endl;
}
Person(int a,int height)//有参构造
{
age = a;
p_hight = new int(height);
cout << "person构造函数的有参调用" << endl;
}
Person(const Person& p)//拷贝构造
{
age = p.age;
//p_hight = p.p_hight;//编译器默认实现方式
p_hight = new int(*p.p_hight);
cout << "person构造函数的深拷贝调用" << endl;
}
//析构函数
~Person()
{
//将堆区数据释放
if (p_hight != NULL)
{
delete p_hight;
p_hight = NULL;
}
cout << "person析构函数的调用" << endl;
}
int age;
int* p_hight;
};
void test01()
{
Person p1(18,160);
cout << "p1的年龄为:" << p1.age << endl;
cout << "p1的身高为:" << *p1.p_hight << endl;
Person p2(p1);
cout << "p2的年龄为:" << p2.age << endl;
cout << "p2的身高为:" << *p2.p_hight << endl;
}
int main()
{
test01();
return 0;
}
10.2.6、初始化列表
语法:构造函数():属性1(值1),属性2(值2);
#include<iostream>
#include<string.h>
using namespace std;
class person
{
public:
int a;
int b;
int c;
person(int n1,int n2,int n3):a(n1),b(n2),c(n3)
{
}
};
int main()
{
person p(10,30,20);
cout << p.a << p.b << p.c << endl;
}
10.2.7、类对象作为类的成员
C++类中的成员可以是另一个类的对象,成为对象成员。
class A{}
class B
{
A a;
}
#include<iostream>
#include<string.h>
using namespace std;
class phone
{
public:
string m_pname;
phone(string pname) :m_pname(pname)
{
cout << "phone构造" << endl;
}
~phone()
{
cout << "phone析构" << endl;
}
};
class person
{
public:
string m_name;
phone m_phone;
person(string name, string pname) :m_name(name),m_phone(phone(pname))
{
cout << "person构造" << endl;
}
~person()
{
cout << "person析构" << endl;
}
};
//其他类对象作为本类成员,先构造其他类对象,在构造自身。
//析构时,先析构自身,后析构对象成员。与构造相反
void test01()
{
person p("ww", "iphone");
cout << p.m_name << " use " << p.m_phone.m_pname<< endl;
}
int main()
{
test01();
return 0;
}
10.2.8、静态成员
static关键字修饰的成员,有静态变量和静态成员。
- 静态成员变量:
- 所有对象共享同一份资源
- 在编译阶段分配内存
- 类内声明,类外初始化
#include<iostream> #include<string.h> using namespace std; class person { public: static int a; //静态成员变量也有访问权限 private: static int b; }; //其他类对象作为本类成员,先构造其他类对象,在构造自身。 //析构时,先析构自身,后析构对象成员。与构造相反 int person::a=100;//类外初始化 int person::b = 100;//类外初始化 void test01() { person p; cout << p.a << endl; person p2; p2.a = 200; cout << p.a << endl;//通过p2修改,p的静态变量a也修改了 //cout << p.b << endl;//类外访问不到私有成员变量 } void test02() { //静态成员变量不属于某个对象,所有对象共享同一份数据 //因此两种访问方式 //通过对象访问 person p; cout << p.a << endl; //通过类名访问 cout << person::a << endl; } int main() { //test01(); test02(); return 0; }
- 静态成员函数:
- 所有对象只能共享一个函数
- 静态成员函数只能访问静态成员变量
#include<iostream> #include<string.h> using namespace std; class person { public: static void func() { a = 100; //b = 0;//静态成员函数不可以访问非静态成员变量,无法区分是那个对象的b成员 cout << "static void func" << endl; } static int a; int b; //静态成员函数也有访问权限 private: static void func1() { cout << "static void func" << endl; a = 10; } }; int person::a = 0; void test01() { person p; //两种访问方式 cout << person::a << endl; p.func(); person::func(); cout << person::a << endl; //person::func1();//私有权限,类外不可访问 } int main() { test01(); return 0; }
10.3、C++对象模型和this指针
10.3.1、成员变量和成员函数分开存储:
在C++中,类内的成员变量和成员函数分开存储,只有非静态成员变量才属于类的对象上。
空对象占用内存空间大小为1,C++为空对象分配1个字节空间,是为了区分空对象占内存的位置。
对象非空时,按照非静态成员变量的内存和计算。——注意内存对齐
静态成员变量不属于类的对象,不影响类的大小。
非静态成员函数和静态成员函数也不属于类的对象,不影响类的空间。
#include<iostream>
using namespace std;
class person
{
int a;
static int b;
void func()
{
}
static void func2()
{
}
};
int person::b = 10;
void test01()
{
person p;
cout << "size of p is: " << sizeof(p) << endl;
}
int main()
{
test01();
return 0;
}
10.3.2、this指针:
this指针指向被调用的成员函数所属的对象。区分不同的调用成员函数的对象。
this指针指向调用成员函数的对象,隐含在每个非静态成员中,不需要定义,直接使用就可以。
this指针的用途:
- 当形参和成员变量函数同名时,用this区分
- 在类的非静态成员函数返回对象本身时,使用return *this
10.3.3、空指针访问成员函数:
空指针可以调用成员函数,但是需要注意有没有使用this指针,如果使用了this指针,需要加以判断保证代码的健壮性。
#include<iostream>
using namespace std;
class person
{
public:
int age;
void showClassName()//空指针可以调用没有属性的成员函数
{
cout << "this is person class" << endl;
}
void showpersonAge()
{
if (this == NULL)
{
return;//加入这个判断后,空指针也可以访问这个函数
}
cout << "age = " << age << endl;
}
};
void test01()
{
person* p = NULL;
p->showClassName();
p->showpersonAge();//传入指针为空,无法访问p->age。
}
int main()
{
test01();
return 0;
}
10.3.4、const修饰成员函数
常函数:
- 成员函数加上const后称为常函数
- 常函数内不可修改成员属性
- 成员属性声明是加关键字mutable后,常函数中可以修改
常对象:
- 声明对象前加const称为常对象
- 常对象只能调用常函数。
#include<iostream>
using namespace std;
class person
{
public:
void showperson() const
{
//a = 100;加入const后无法修改普通成员
b = 100;//加入const后可以修改mutable成员
cout << a << endl;
cout << b << endl;
}
void func()
{
}
int a;
mutable int b;
};
void test01()
{
person p;
p.showperson();
}
void test01()
{
const person p;
//p.a = 100;//常对象无法修改
p.b = 200;//mutable常对象可以改
p.showperson();
//p.func();//常对象无法调用普通成员函数。
}
int main()
{
test01();
return 0;
}
10.4、友元
在程序中,有些私有属性可以让类外特殊的函数或者类进行访问,友元的目的是让一个函数或者类方位另一个类中私有成员。关键字:friend
友元实现方法:
- 全局函数做友元
- 类做友元
- 成员函数做友元。
10.4.1、全局函数做友元
#include<iostream>
#include<string>
using namespace std;
//房屋类
class building
{
friend void goodGay(building* build);//goodgay全局函数是building的友元
public:
building()
{
SittingRoom = "客厅";
bedRoom = "卧室"; }
public:
string SittingRoom;
private:
string bedRoom;
};
//全局函数
void goodGay(building* build)
{
cout << "好基友的全局函数正在访问:" << build->SittingRoom << endl;
cout << "好基友的全局函数正在访问:" << build->bedRoom << endl;
}
void test01()
{
building build;
goodGay(&build);
}
int main()
{
test01();
return 0;
}
10.4.2、类做友元
#include<iostream>
#include<string>
using namespace std;
//房屋类
class building
{
friend class GoodGay;//GoodGay类是building类的友元
public:
building();
public:
string SittingRoom;
private:
string bedRoom;
};
//类外写成员函数
building::building()
{
SittingRoom = "客厅";
bedRoom = "卧室";
cout << "building构造" << endl;
}
//类做友元
class GoodGay
{
public:
void visit();//参观函数 访问building中的属性
GoodGay();
building* build;
};
GoodGay::GoodGay()
{
cout << "goodgay 构造" << endl;
build = new building;
}
void GoodGay::visit()
{
cout << "好基友类正在访问:" << build->SittingRoom << endl;
cout << "好基友类正在访问:" << build->bedRoom << endl;
}
void test01()
{
GoodGay gg;
gg.visit();
}
int main()
{
test01();
return 0;
}
10.4.3、成员函数做友元
#include<iostream>
#include<string>
using namespace std;
class building;
class GoodGay
{
public:
GoodGay();
building* build;
void visit();//可以访问building中私有成员
void visit2();//不可以访问building中私有成员
};
class building
{
friend void GoodGay:: visit();//GoodGay类下的visit成员函数是building的友元
public:
string sittingRoom;
building();
private:
string bedRoom;
};
building::building()
{
sittingRoom = "客厅";
bedRoom = "卧室";
cout << "building构造" << endl;
}
GoodGay::GoodGay()
{
cout << "GoodGay构造" << endl;
build = new building;
}
void GoodGay:: visit()
{
cout << "visit()正在访问:" << build->sittingRoom << endl;
cout << "visit()正在访问:" << build->bedRoom << endl;
}
void GoodGay::visit2()
{
cout << "visit2()正在访问:" << build->sittingRoom << endl;
//cout << "visit2()正在访问:" << build->bedRoom << endl;
}
void test01()
{
GoodGay gg;
gg.visit();
gg.visit2();
}
int main()
{
test01();
return 0;
}
10.5、运算符重载
对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
使用operator关键字,运算符重载也可以发生函数重载。对于内置数据类型,不能改变运算方式。
10.5.1、加号运算符重载
#include<iostream>
#include<string>
using namespace std;
class person
{
public:
int a;
int b;
//成员函数实现
/*person operator+(person& p)
{
person temp;
temp.a = this->a + p.a;
temp.b = this->b + p.b;
return temp;
}*/
//成员函数本质调用:
//p3=p1.operator+(p2);
};
//全局函数实现+
person operator+(person &p1, person &p2)
{
person temp;
temp.a = p1.a + p2.a;
temp.b = p1.b + p2.b;
return temp;
}
//全局函数本质调用:
//p3=operator(p1,p2);
//运算符重载也可以发生函数重载
person operator+(person& p1, int num)
{
person temp;
temp.a = p1.a + num;
temp.b = p1.b + num;
return temp;
}
void test01()
{
person p1;
p1.a = 10;
p1.b = 5;
person p2;
p2.a = 10;
p2.b = 5;
person p3;
p3 = p1 + p2;
person p4 = p3 + 100;
cout << "p3.a=" << p3.a << endl;
cout << "p3.b=" << p3.b << endl;
cout << "p4.a=" << p4.a << endl;
cout << "p4.b=" << p4.b << endl;
}
int main()
{
test01();
return 0;
}
10.5.2、左移运算符重载
作用:可以输出自定义的数据类型。
#include<iostream>
#include<string>
using namespace std;
class person
{
public:
int a;
int b;
//利用成员函数重载左移
//不会利用成员函数重载左移运算符,因为无法实现cout在左侧
/*void operator << (ostream &cout)
{
cout << "a= " << this->a << endl;
cout << "b= " << this->b << endl;
}*/
};
//只能利用全局函数重载左移运算符
ostream& operator<<(ostream &cout,person &p)
{
cout << "a= " << p.a;
cout << ",b= " <<p.b;
return cout;
}
void test01()
{
person p;
p.a = 10;
p.b = 5;
cout << p <<"hello wprld"<< endl;
//p << cout;//成员函数重载左移运算符
}
int main()
{
test01();
return 0;
}
可以让成员属性私有化,然后使用运算符全局重载函数做友元进行访问打印。
10.5.3、递增运算符重载
作用:通过递增运算符,实现自己的整型数据
#include<iostream>
#include<string>
using namespace std;
//实现自己的整数
class myInteger
{
friend ostream& operator<<(ostream& cout, myInteger myint);
public:
myInteger()
{
m_num = 0;
}
//前置递增
myInteger& operator++()
{
m_num++;
return *this;
}
//后置递增
myInteger operator++(int)//int代表占位参数,可以区分前置和后置递增
{
myInteger temp = *this;
m_num++;
return temp;
}
private:
int m_num;
};
//左移运算符
ostream& operator<<(ostream& cout, myInteger myint)
{
cout << myint.m_num;
return cout;
}
void test01()
{
myInteger myint;
cout << myint << endl;
cout << ++(++myint) << endl;
cout << myint << endl;
}
void test02()
{
myInteger myint;
cout << myint << endl;
cout << myint++ << endl;
cout << myint << endl;
}
int main()
{
test01();
test02();
return 0;
}
10.5.4、赋值运算符重载
c++编译器至少给一个类添加4个函数
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属作进行值拷贝
4.赋信运管符 operator=,对属件进行值拷贝——C++默认提供的是浅拷贝
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题
#include<iostream>
#include<string>
using namespace std;
class person
{
public:
person(int age)
{
m_age = new int(age);//开辟堆区数据
}
int* m_age;
~person()
{
if (m_age != NULL)
{
delete m_age;
}
}
person& operator=(person &p)
{
if (m_age != NULL)
{
delete m_age;
m_age = NULL;
}
m_age = new int(*p.m_age);//深拷贝。
return *this;
}
};
void test01()
{
person p1(18);
cout << "p1的年龄为:" << *p1.m_age << endl;
person p2(20);
person p3(30);
cout << "p2的年龄为:" << *p2.m_age << endl;
p3 = p2 = p1;//赋值操作——C++默认赋值操作为浅拷贝
cout << "p1的年龄为:" << *p1.m_age << endl;
cout << "p2的年龄为:" << *p2.m_age << endl;
cout << "p3的年龄为:" << *p3.m_age << endl;
}
int main()
{
test01();
return 0;
}
10.5.5、关系运算符重载
这里以==和!=为例。其余的自己可以实现一下。
#include<iostream>
using namespace std;
class person
{
public:
int m_age;
string name;
person(string n, int age)
{
m_age = age;
name = n;
}
bool operator==(person p)
{
return m_age == p.m_age&&name==p.name;
}
bool operator!=(person p)
{
return !(m_age == p.m_age && name == p.name);
}
};
void test01()
{
person p1("zxx", 24);
person p2("zxxw", 24);
if (p1 == p2)
{
cout << "p1 is equal to p2" << endl;
}
else
{
cout << "p1 is not equal to p2" << endl;
}
}
void test02()
{
person p3("zxx", 24);
person p4("zxxw", 24);
if (p3 != p4)
{
cout << "p3 is not equal to p4" << endl;
}
else
{
cout << "p3 is equal to p4" << endl;
}
}
int main()
{
test01();
test02();
return 0;
}
10.5.6、函数调用运算符重载——仿函数
#include<iostream>
#include<string>
using namespace std;
//打印输出类
class Myprint
{
public:
void operator()(string test)
{
cout << test << endl;
}
};
//加法类
class MyAdd
{
public:
int operator()(int a, int b)
{
return a + b;
}
};
void myprint02(string test)
{
cout << test << endl;
}
void test01()
{
Myprint myprint;
myprint("hello world");//使用非常像函数调用
myprint02("hello world");
MyAdd myadd;
int ret = myadd(100, 200);
cout << "ret = " << ret << endl;
//匿名函数对象
cout << MyAdd()(100, 100) << endl;
}
int main()
{
test01();
return 0;
}
10.6、继承
继承是面向对象的三大特性之一,有些类和类之间有特殊的关系,定义这些类时,下级别成员除了拥有上一级的共性,还有自己的特性,可以使用继承技术,减少重复代码。
10.6.1、继承的基本语法
class 子类名:继承方式 父类名{}
子类也叫派生类,父类也叫基类
#include<iostream>
#include<string>
using namespace std;
//普通页面实现
class BasePage
{
public:
void header()
{
cout << "首页、公开课、注册、登陆(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、C++(公共分类列表)" << endl;
}
};
//Java页面
class Java :public BasePage
{
public:
void content()
{
cout << "Java 学科视频" << endl;
}
};
class Python :public BasePage
{
public:
void content()
{
cout << "Python 学科视频" << endl;
}
};
class Cpp :public BasePage
{
public:
void content()
{
cout << "C++ 学科视频" << endl;
}
};
void test01()
{
cout << "Java 下载视频页面如下" << endl;
Java ja;
ja.header();
ja.footer();
ja.left();
ja.content();
cout << "-----------------" << endl;
cout << "Python 下载视频页面如下" << endl;
Python py;
py.header();
py.footer();
py.left();
py.content();
cout << "-----------------" << endl;
cout << "C++ 下载视频页面如下" << endl;
Cpp c;
c.header();
c.footer();
c.left();
c.content();
}
int main()
{
test01();
return 0;
}
10.6.2、继承方式
继承方式一共有3种:
- 公共继承:public
- 保护继承:protect
- 私有继承:private
父类的私有属性,子类均不可访问。
公共继承不改变父类的属性的权限。
保护继承和私有继承将可以继承的属性全部改为保护权限和私有权限。
#include<iostream>
#include<string>
using namespace std;
class Base
{
public:
int a;
protected:
int b;
private:
int c;
};
class Son1 : public Base
{
public:
void func()
{
a = 10;
b = 10;
//c = 10;//c不可访问
}
};
class Son2 :protected Base
{
public:
void func()
{
a = 10;
b = 10;
//c = 10;//c不可访问
}
};
class Son3 :private Base
{
void func()
{
a = 10;
b = 10;
//c = 10;//c不可访问
}
};
class Grandson3 :public Son1//Son1为公共继承,不改变a和b的属性
{
void func()
{
a = 10;
b = 10;
//c = 10;//c不可访问
}
};
//Son2为保护继承,故Son2所有成员属性都是保护,其子类Grandson1可以继承属性
class Grandson1 :public Son2
{
void func()
{
a = 10;
b = 10;
//c = 10;//c不可访问
}
};
//Son3为私有继承,故Son3所有成员属性都是私有,其子类Grandson2无法继承属性
class Grandson2 :public Son3
{
void func()
{
//a = 10;
//b = 10;
//c = 10;//c不可访问
}
};
void test01()
{
Son1 son;
son.a = 100;//a仍是public权限,类Son2外可以访问
//son.b = 10;//b是protected权限,类Son2外不可访问
}
void test02()//Son2为保护继承,故Son2所有成员属性都是保护,
{
Son2 son;
//son.a = 100;//a是protected权限,类Son2外不可访问
//son.b = 10;//b是protected权限,类Son2外不可访问
}
void test03()//Son3为私有继承,故B3所有成员属性都是私有
{
Son3 son;
//son.a = 100;//a是private权限,类Son2外不可访问
//son.b = 10;//b是private权限,类Son2外不可访问
}
int main()
{
return 0;
}
10.6.3、继承中的对象模型
子类会继承父类的所有成员,其中私有(private)属性会被编译器自动隐藏。
#include<iostream>
#include<string>
using namespace std;
class Base
{
public:
int a;
protected:
int b;
private:
int c;
};
class son :public Base
{
public:
int d;
};
void test01()
{
son s;
cout << "size of (son) = " << sizeof(s) << endl;
//根据输出结果为16可以得知,子类会继承父类的所有成员
}
int main()
{
test01();
return 0;
}
10.6.4、继承中构造和析构顺序
构造时先构造父类,析构时,先析构子类
#include<iostream>
#include<string>
using namespace std;
class Base
{
public:
Base()
{
cout << "This is Base 构造" << endl;
}
~Base()
{
cout << "This is Base 析构" << endl;
}
};
class son :public Base
{
public:
son()
{
cout << "This is son 构造" << endl;
}
~son()
{
cout << "This is son 析构" << endl;
}
};
void test01()
{
//Base b;
son s;
}
int main()
{
test01();
return 0;
}
10.6.5、继承同名处理方式
访问子类同名成员:直接访问
访问父类同名成员:需要加作用域
#include<iostream>
#include<string>
using namespace std;
class Base
{
public:
int a;
Base()
{
a = 100;
}
void func()
{
cout << "Base _func()" << endl;
}
void func(int b)
{
cout << "Base _func(int b)" << endl;
}
};
class Son:public Base
{
public:
int a;
Son()
{
a = 200;
}
void func()
{
cout << "Son _func()" << endl;
}
};
void test01()
{
Son s;
cout << "s.a= " << s.a << endl;//直接访问子类成员
cout << "s.Base::a= " << s.Base::a << endl;//访问父类成员
}
void test02()
{
Son s;
s.func();
s.Base::func();
//如果子类和父类出现同名的成员函数,子类的同名成员会隐藏掉父类所有重名函数,包括函数重载
s.Base::func(100);
}
int main()
{
//test01();
test02();
return 0;
}
10.6.6、同名静态成员处理
访问子类同名成员:直接访问即可
访问父类同名成员:需要加作用域
#include<iostream>
#include<string>
using namespace std;
class Base
{
public:
static int a;
static void func()
{
cout << "Base static func()" << endl;
}
};
int Base::a = 100;
class Son :public Base
{
public:
static int a;
static void func()
{
cout << "Son static func()" << endl;
}
};
int Son::a = 200;
void test01()
{
//通过对象访问
Son s;
cout << "s.a=" << s.a << endl;
cout << "s.Base::a=" << s.Base::a << endl;
//通过类名访问
cout << "Son-a=" << Son::a << endl;
cout << "Base-a=" << Son::Base::a << endl;
//第一个::表示通过类名访问,第二个::表示父类作用域
}
void test02()
{ //通过对象访问
Son s;
s.func();
s.Base::func();
//通过类名访问
Son::func();
Son::Base::func();
}
int main()
{
//test01();
test02();
return 0;
}
10.6.7、多继承语法
C++允许一个类继承多个类,用逗号分割。父类同名成员出现需要加作用域。
#include<iostream>
#include<string>
using namespace std;
class BaseA
{
public:
int a;
BaseA()
{
a = 100;
}
};
class BaseB
{
public:
int a;
BaseB()
{
a = 200;
}
};
class Son :public BaseA, public BaseB
{
public:
int c;
int a;
Son()
{
a = 300;
c = 400;
}
};
void test01()
{
cout << "sizeof(Son)=" << sizeof(Son) << endl;
Son s;
cout << "s.BaseA::a=" << s.BaseA::a << endl;
cout << "s.BaseB::a=" << s.BaseB::a << endl;
cout << "s.a=" << s.a << endl;
}
int main()
{
test01();
return 0;
}
10.6.8、菱形继承
两个派生类,继承同一个基类。
然后一个子类继承两个派生类。
菱形继承的问题:
- 子类继承了两次最初基类的信息,会有二义性————通过作用域解决。
- 继承的两份基类的数据,产生重复。————通过虚继承解决,virtual关键字,继承了指针,而不是直接继承数据
10.7、多态
10.7.1、多态的分类
C++多态分为两类:
静态多态:函数重载和运算符重载——函数地址早绑定,编译阶段确定地址
动态多态:派生类和虚函数实现运行时多态——函数地址晚绑定,运行阶段确定地址。
动态多态满足条件:
- 有继承关系
- 子类要重写父类的虚函数(同名,同返回值,同形参列表),父类一定要加virtual关键字,子类可以写,可以不写
- 动态多态的使用:父类的引用或者指针,指向子类对象。
#include<iostream>
#include<string>
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;
}
};
//执行说话
//地址早绑定,编译阶段确定函数的地址,如果想执行Cat::speak(),函数地址必须运行时确定,用virtual关键字修饰函数
void doSpeak(Animal& animal)
{
animal.speak();
}
void test01()
{
Cat cat;
doSpeak(cat);//&animal=cat;//运行父子之间类型转换
Dog dog;
doSpeak(dog);
}
int main()
{
test01();
return 0;
}
10.7.2、多态的底层实现
子类重写父类的虚函数时,子类的虚函数表内部会替换成子类的虚函数。
10.7.3、多态案例一——计算器类
多态的优点:
- 代码组织结构清晰
- 可读性强
- 利于前期和后期的扩展以及维护
#include<iostream>
#include<string>
using namespace std;
//普通实现
class Calculator
{
public:
int m_Num1;
int m_num2;
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;
}
}
};
//多态实现
//实现计算器的基类,抽象类
class AbstractCalculator
{
public:
virtual int getresult()
{
return 0;
}
int m_Num1;
int m_Num2;
};
//加法计算类
class AddCalculator :public AbstractCalculator
{
public:
virtual int getresult()
{
return m_Num1 + m_Num2;
}
};
//减法计算器类
class SubCalculator :public AbstractCalculator
{
public:
virtual int getresult()
{
return m_Num1 - m_Num2;
}
};
//乘法计算器类
class MulCalculator :public AbstractCalculator
{
public:
virtual int getresult()
{
return m_Num1 * m_Num2;
}
};
void test01()
{
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;
}
void test02()
{
AbstractCalculator* abc = new AddCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 20;
cout << "abc->m_Num1+abc->m_Nmu2 = " << abc->getresult() << endl;
delete abc;
abc = new SubCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 20;
cout << "abc->m_Num1-abc->m_Nmu2 = " << abc->getresult() << endl;
delete abc;
abc = new MulCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 20;
cout << "abc->m_Num1*abc->m_Nmu2 = " << abc->getresult() << endl;
}
int main()
{
//test01();
test02();
return 0;
}
10.7.4、纯虚函数和抽象类
纯虚函数写法:virtual 数据类型 函数名(参数列表)=0;
有纯虚函数的类为抽象类。
特点:
- 无法实例化对象
- 子类必须重新纯虚函数,否则子类也属于抽象类
#include<iostream>
#include<string>
using namespace std;
class Base
{
public:
virtual void func() = 0;//纯虚函数,为抽象类
};
class Son :public Base
{
public:
void func()
{
cout << "func() in Son" << endl;
}
};
void test01()
{
//Base b;//抽象类无法实例化对象。
Son s;//只有子类重写纯虚函数才能实例化对象
Base* base = new Son;
base->func();
}
int main()
{
test01();
return 0;
}
10.7.5、多态案例二:制作饮品
制作饮品的流程为:煮水、冲泡、倒入杯中、加入辅料
利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶
#include<iostream>
#include<string>
using namespace std;
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 Coffee :public AbstractDrinking
{
public:
//煮水
virtual void Boil()
{
cout << "boil water" << endl;
}
//冲泡
virtual void Brew()
{
cout << "make coffee" << endl;
}
//倒入杯中
virtual void PourInCup()
{
cout << "put in cup" << endl;
}
//加入辅料
virtual void PutSomething()
{
cout << "put milk " << endl;
}
};
class Tea :public AbstractDrinking
{
public:
//煮水
virtual void Boil()
{
cout << "boiled water" << endl;
}
//冲泡
virtual void Brew()
{
cout << "make Tea" << endl;
}
//倒入杯中
virtual void PourInCup()
{
cout << "put in glass cup" << endl;
}
//加入辅料
virtual void PutSomething()
{
cout << "put nothing " << endl;
}
};
void dowork(AbstractDrinking *abs)
{
abs->makeDrink();
delete abs;
}
void test01()
{
dowork(new Coffee);
cout << "------------------" << endl;
dowork(new Tea);
}
int main()
{
test01();
return 0;
}
10.7.6、虚析构和纯虚析构
多态使用时,如果子类有属性开辟到堆区,父类指针在释放时无法调用子类的析构代码。
解决方式:将父类析构函数变为虚析构或者纯虚析构。
#include<iostream>
#include<string>
using namespace std;
class Animal
{
public:
Animal()
{
cout << "Animal 构造" << endl;
}
virtual void speak() = 0;
//虚析构
/*virtual ~Animal()
{
cout << "animal 析构" << endl;
}*/
//纯虚析构——————需要代码实现
virtual ~Animal() = 0;
};
Animal::~Animal()
{
cout << "animal 纯虚析构" << endl;
}
class Cat :public Animal
{
public:
Cat(string name)
{
cout << "Cat 构造" << endl;
m_Name = new string(name);
}
virtual void speak()
{
cout << "猫猫在说话" << endl;
}
virtual ~Cat()//父类不使用虚析构时,不会执行cat析构函数。
{
if (m_Name != NULL)
{
cout << "Cat 析构" << endl;
delete m_Name;
m_Name = NULL;
}
}
string *m_Name;
};
void test01()
{
Animal* animal = new Cat("Tom");
animal->speak();
delete animal;
}
int main()
{
test01();
system("pause");
return 0;
}
10.7.7、多态案例三电脑组装
案例描述:
CPU、显卡、内存条
每个零件封装成不同基类,提供不同厂商零件——Intel和Lenovo
创建电脑类,让电脑工作
测试组装三台不同的电脑
#include<iostream>
#include<string>
using namespace std;
class AbstractCpu
{
public:
virtual void calculate() = 0;
virtual ~AbstractCpu()
{
cout << "delete cpu" << endl;
}
};
//厂商
class InterCPU :public AbstractCpu
{
public:
void calculate()
{
cout << "This is Inter Cpu;" << endl;
}
~InterCPU()
{
cout << "nothing" << endl;
}
};
class LenovoCPU :public AbstractCpu
{
public:
void calculate()
{
cout << "This is Lenovo Cpu;" << endl;
}
};
//GPU
class AbstractGpu
{
public:
virtual void display() = 0;
};
class InterGpu :public AbstractGpu
{
public:
void display()
{
cout << "This is Inter Gpu;" << endl;
}
};
class LenovoGpu :public AbstractGpu
{
public:
void display()
{
cout << "This is Lenovo Gpu;" << endl;
}
};
//Mem
class AbstractMem
{
public:
virtual void storage() = 0;
};
class InterMem :public AbstractMem
{
public:
void storage()
{
cout << "This is Inter Mem;" << endl;
}
};
class LenovoMem :public AbstractMem
{
public:
void storage()
{
cout << "This is Lenovo Mem;" << endl;
}
};
//电脑类
class computer
{
private:
AbstractCpu* m_cpu;
AbstractGpu* m_gpu;
AbstractMem* m_mem;
public:
computer(AbstractCpu* cpu, AbstractGpu* gpu, AbstractMem* mem)
{
m_cpu = cpu;
m_gpu = gpu;
m_mem = mem;
}
void dowork()
{
m_cpu->calculate();
m_gpu->display();
m_mem->storage();
}
~computer()
{
if (m_cpu != NULL)
{
delete m_cpu;
m_cpu = NULL;
}
if (m_gpu != NULL)
{
delete m_gpu;
m_gpu = NULL;
}
if (m_mem != NULL)
{
delete m_mem;
m_mem = NULL;
}
}
};
void test01()
{
AbstractCpu* cpu = new InterCPU;
AbstractGpu* gpu = new InterGpu;
AbstractMem* mem = new InterMem;
computer* com = new computer(cpu, gpu, mem);
com->dowork();
delete com;
}
void test02()
{
AbstractCpu* cpu = new LenovoCPU;
AbstractGpu* gpu = new LenovoGpu;
AbstractMem* mem = new LenovoMem;
computer* com = new computer(cpu, gpu, mem);
com->dowork();
delete com;
}
void test03()
{
computer* com = new computer(new InterCPU, new LenovoGpu, new LenovoMem);
com->dowork();
delete com;
}
int main()
{
cout << "------computer1-----" << endl;
test01();
cout << "------computer2-----" << endl;
test02();
cout << "------computer3-----" << endl;
test03();
return 0;
}
十一、文件操作
程序运行时的数据是临时数据。通过文件可以将数据持久化。
C++中对文件操作需要包含头文件fstream
文件分为两种:文本文件——ASCII码存储。二进制文件:文本以二进制存储。
操作文件的三大类:ofstream:写操作。ifstream:读操作。fstream:读写操作。
11.1.1、写文件
文件打开方式:
打开方式 | 解释 |
ios::in | 为读文件而打开文件 |
ios::out | 为写文件而打开文件 |
ios::ate | 初始位置:文件尾 |
ios::app | 追究方式写文件(先去文件尾) |
ios::trunc | 如果文件存在先删除,在创建 |
ios::binary | 二进制文件 |
#include<iostream>
#include<fstream>
using namespace std;
void test01()
{
//创建流文件
ofstream ofs;
//打开方式
ofs.open("test.txt", ios::out);
ofs << "姓名:张三" << endl;
ofs << "性别:男" << endl;
ofs << "年龄:18" << endl;
ofs.close();
}
int main()
{
test01();
return 0;
}
11.1.2、读文件
#include<iostream>
#include<fstream>
#include<string>
using namespace std;
void test01()
{
//创建流文件
ifstream ifs;
//打开方式,判断是否打开成功
ifs.open("test.txt", ios::in);
if (!ifs.is_open())
{
cout << "文件打开失败" << endl;
return;
}
//读文件内容
//方法1
/*char buf[1024] = { 0 };
while (ifs>>buf)
{
cout << buf << endl;
}*/
//方法2
/*char buf[1024] = { 0 };
while (ifs.getline(buf, sizeof(buf)))
{
cout << buf << endl;
}*/
//方法3
/*string buf;
while (getline(ifs, buf))
{
cout << buf << endl;
}*/
//方法4
char c;
while ((c=ifs.get())!=EOF)//不推荐
{
cout << c;
}
ifs.close();
}
int main()
{
test01();
return 0;
}
11.2.1、写二进制文件
打开方式指定为:ios::binary
使用write()写数据
#include<iostream>
#include<fstream>
using namespace std;
class person
{
public:
char name[64];
int age;
};
void test01()
{
//创建流文件
ofstream ofs;
//打开方式
ofs.open("person.txt", ios::out|ios::binary);
//写文件
person p={"zxx",18};
p.age = 18;
ofs.write((const char*)&p,sizeof(person));
ofs.close();
}
int main()
{
test01();
return 0;
}
11.2.2、读二进制文件
#include<iostream>
#include<fstream>
using namespace std;
class person
{
public:
char name[64];
int age;
};
void test01()
{
//创建流文件
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 << "name :" << p.name << "age :" << p.age << endl;
ifs.close();
}
int main()
{
test01();
return 0;
}