赞
踩
本阶段主要针对C++面向对象的核心和精髓。
C++程序执行时,将内存大方向划分为4个区域
内存分区的意义:
不同区域存放的数据,生命周期不同,给我们更大的灵活编程。
C++中利用new操作符在堆区申请空间,用delete释放空间(由程序员申请和释放)。
int *func(){
int *a=new int(10); //申请空间
return a;
}
int main(){
int *p=func();
cout<<*p<<endl;
delete p; //释放数据
int *arr=new int[10];
for(int i=0;i<10;++i)
arr[i]=100+i;
delete [] arr; //释放数组
return 0;
}
作用:给变量起别名
语法:数据类型 & 别名=原名
int main(){
int a=10;
int &b=a;
cout<<a<<endl; //10
cout<<b<<endl; //10
b=100;
cout<<a<<endl; //100
cout<<b<<endl; //100
}
使用引用b的值改变会影响a。
引用必须初始化!!!
引用在初始化后,不可以改变(比如c最开始是a的引用,就不能再改为b的引用)!!!
int main(){
int a=10;
int b=20;
// int &c; //定义时不初始化会报错
int &c=a;
cout<<a<<endl; //10
cout<<b<<endl; //20
cout<<c<<endl; //10
c=b; //赋值操作,把b的值赋给c,并不是更改引用哈。
cout<<a<<endl; //20
cout<<b<<endl; //20
cout<<c<<endl; //20
}
//值传递
void swap1(int x,int y){
int temp=x;
x=y;
y=temp;
}
//地址传递
void swap2(int *p1,int *p2){
int temp=*p1;
*p1=*p2;
*p2=temp;
}
//引用传递
void swap3(int &p1,int &p2){
int temp=p1;
p1=p2;
p2=temp;
}
int main() {
int a=10;
int b=20;
swap1(a,b); //值传递不会改变值
cout<<a<<endl; // 10
cout<<b<<endl; // 20
swap2(&a,&b); //传递地址会改变值
cout<<a<<endl; // 20
cout<<b<<endl; // 10
swap3(a, b); //引用传递会改变值
cout<<a<<endl; // 10
cout<<b<<endl; // 20
return 0;
}
注意:通过引用参数产生的效果同地址传递是一样的,引用的语法更简单清楚。
注意:引用是可以作为函数的返回值存在的。
不要返回局部变量引用。
//返回局部变量引用
int & test1(){
int a=10; //局部变量
return a; //会提示: warning: reference to stack memory associated with local variable 'a' returned,局部变量是放在栈区的,代码运行结束后会释放。
}
//返回静态变量引用
int & test2(){
static int a=20; //静态变量存放在全局区,全局区上的数据在程序结束之后才由系统释放;
return a;
}
int main() {
int & ref=test1();
cout<<ref<<endl; //10第一次运行结果正确,因为编译器做了保留。
cout<<ref<<endl; //第二次就运行错误,因为a的内存已经释放。
int &ref2=test2();
cout<<ref2<<endl; //20
test2()=1000;
cout<<ref2<<endl; //1000
return 0;
}
所以说引用一般只做为左值。
引用的本质在C++内部实现是一个指针常量。
void func(int &p){
p=100; //C+发现p是引用,自动转换为*p=1000;
}
int main() {
int a=10;
int &ref=a;
//C+发现ref是引用,自动转换为int* const ref=&a;指针常量是指针指向不可以改变,但是指向的值可以改变。
ref=20; //内部发现ref为引用,自动转换为* ref=20;
cout<<a<<endl; //20
cout<<ref<<endl; //20
func(a);
cout<<a<<endl; //100
cout<<ref<<endl; //100
return 0;
}
结论:C++推荐引用技术,因为语法方便,引用本质是指针常量,但是所有的指针操作编译器都帮我们做了。
常量引用主要用来修饰形参,防止误操作。
//引用使用的场景,通常用来修饰形参
void showValue(const int &p){
// p=20; p为只读,不可修改。
// p+=10; 使用const修饰无法进行+=操作
cout<<p<<endl;
}
int main() {
// int & ref=10; 错误,因为引用本身需要一个合法的内存空间,所以错误;
//可以在前面加入const,编译器优化代码,int temp=10,const int & ref=temp;
const int & ref=10; //加入const之后不能修改变量
cout<<ref<<endl;
int a=10;
showValue(a);
return 0;
}
在C++中,函数的形参列表中的形参是可以有默认值的。
语法: 返回值类型 函数名 (参数=默认值){}
1.如果从某个位置参数有默认值,那么从这个位置往后,从左向右,必须都要有默认值;
2.如果函数声明有默认值,函数实现的时候就不能有默认参数;
int func1(int a,int b=10,int c=20){
return a+b+c;
}
int func2(int a=10,int b=20); //函数声明
int func2(int a,int b){ //函数实现
return a+b;
}
int main() {
cout<<func1(10,30,40)<<endl; //80
cout<<func1(10)<<endl; //40
cout<<func2(30,50)<<endl; //80
cout<<func2()<<endl; //30
return 0;
}
C++中函数的形参列表里可以有占位参数,用来占位,调用函数时必需填补该位置。
//函数占位参数,占位参数也可以有默认参数
void func(int a,int){
cout<<a<<endl;
cout<<"this is a func"<<endl;
}
int main() {
func(10, 10);
return 0;
}
作用:函数名可以相同,提高复用性。
函数重载满足条件:
- 同一个作用域下;
- 函数名称相同;
- 函数参数类型不同或者个数不同或者顺序不同;
注意函数返回值类型不可以作为函数重载条件。
void func(){
cout<<"func()"<<endl;
}
void func(int a){
cout<<"func(int a)"<<endl;
}
void func(double a){
cout<<"func(double a)"<<endl;
}
void func(int a,int b){
cout<<"func(int a,int b)"<<endl;
}
int main() {
func();
func(1);
func(10.0);
func(2,3);
return 0;
}
函数重载注意事项:
引用作为重载条件。
函数重载碰到函数默认参数。
//引用作为重载条件
void func(int &a){
cout<<"func(int &a)"<<endl;
}
void func(const int &a){
cout<<"func(const int &a)"<<endl;
}
//函数重载碰到函数默认参数
void func2(int a,int b=10){
cout<<"func2(int a,int b=10)"<<endl;
}
void func2(int a){
cout<<"func2(int a)"<<endl;
}
int main(){
int a=10;
func(a);
func(10);
//func2(10); 此时会出现错误,因为碰到默认参数会产生歧义
return 0;
}
C++面向对象的三大特性为:封装、继承、多态;
封装是C++面向对象三大特性之一,封装的意义:
class Person{
public:
string name;
protected:
string car;
private:
int password;
public:
void func(){
name="张三";
car="拖拉机";
password=123456;
}
};
int main(){
Person p;
p.name="李四";
// p.car="奔驰"; 保护权限类外访问不到
// p.password=1231234; 私有权限类外访问不到
return 0;
}
struct与class的唯一区别就在于默认的访问权限不同。
- struct默认权限为public;
- class默认权限为private;
当使用完一个对象或变量,没有及时清理,也会造成一定的安全问题。C++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。对象的初始化和清理工作是编译器强制需要做的事情,所以即使我们不提供构造和析构,编译器也会提供相应的函数,不过编译器提供的构造函数和析构函数是空实现。
- 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自欧东调用,无须手动调用。
- 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
构造函数语法:类名(){}
析构函数语法:~类名(){}
两种分类方式:
按参数分:有参构造和无参构造
按类型分:普通构造和拷贝构造
三种调用方式:
括号法
显示法
隐式转换法
class Person{
public:
Person(){
cout<<"无参构造函数"<<endl;
};
Person(int a){
cout<<"有参构造函数"<<endl;
};
Person(const Person& p){
cout<<"拷贝构造函数"<<endl;
}
~Person(){
cout<<"析构函数调用"<<endl;
};
int age;
};
int main(){
//括号法
Person p1(10);
//显式法
Person p2=Person(12);
Person p3=Person(p2);
//隐式转换法
Person p4=10; //相当于 Person p4=Person(10);
Person p5=p4; //相等于 Person p5=Person(p4);
return 0;
}
C++中拷贝构造函数调用时机通常有三种情况:
- 使用一个已经创建完毕的对象来初始化一个对象
- 值传递的方式给函数参数传值
- 以值方式返回局部对象
class Person{
public:
Person(){
cout<<"无参构造函数"<<endl;
mage=0;
};
Person(int age){
cout<<"有参构造函数"<<endl;
mage=age;
};
Person(const Person& p){
cout<<"拷贝构造函数"<<endl;
mage=p.mage;
}
~Person(){
cout<<"析构函数调用"<<endl;
};
int mage;
};
//1、使用一个已经创建完毕的对象来初始化一个新对象
void test1(){
Person man(100); //有参构造
Person newman(man); //拷贝构造
Person newman2=man; //拷贝构造
Person newman3; //无参构造
newman3=man; //不是调用拷贝构造,仅是赋值
}
//2、值传递的方式给函数参数传值
void dowork(Person p1){} //相当于Person p1=p;
void test2(){
Person p; //无参构造函数
dowork(p);
}
//3、以值方式返回局部对象
Person dowork2(){
Person p1;
cout<<&p1<<endl;
return p1;
}
void test3(){
Person p=dowork2();
cout<<&p<<endl;
}
int main(){
//test1();
//test2();
test3();
return 0;
}
默认情况下,C++编译器至少给一个类添加3个函数:
- 默认构造函数(无参)
- 默认析构函数
- 默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下:
- 如果用户定义有参构造函数,C++不在提供默认无参构造,但是会提供默认拷贝构造;
- 如果用户定义拷贝构造函数,C++不会再提供其他构造函数。
浅拷贝:简单的赋值拷贝操作;(编译器为我们提供的拷贝构造函数就是简单的浅拷贝)
深拷贝:在堆区重新申请空间,进行拷贝操作。
浅拷贝Part:
class Person{
public:
Person(){
cout<<"无参构造函数"<<endl;
mage=0;
};
Person(int age){
cout<<"有参构造函数"<<endl;
mage=age;
};
~Person(){
cout<<"析构函数调用"<<endl;
};
int mage;
};
int main(){
Person p1(18);
cout<<p1.mage<<endl;
Person p2(p1); //编译器提供的默认拷贝构造函数,为浅拷贝
cout<<p2.mage<<endl;
return 0;
}
深拷贝Part:
class Person{
public:
Person(){
cout<<"无参构造函数"<<endl;
mage=0;
};
Person(int age,int height){
cout<<"有参构造函数"<<endl;
mage=age;
mheight=new int(height);
};
~Person(){
cout<<"析构函数调用"<<endl;
if(mheight!=NULL) //对堆区申请的数据进行释放
delete mheight;
};
int mage;
int *mheight; //需要在申请空间
};
int main(){
Person p1(18,180);
cout<<p1.mage<<" "<<*p1.mheight<<endl;
Person p2(p1);
cout<<p2.mage<<" "<<*p2.mheight<<endl;
return 0;
}
在析构函数中对申请的堆区mheight进行释放,会提示:
free(): double free detected in tcache 2
Aborted (core dumped)
所以:如果属性有在堆区开辟的,为防止浅拷贝带来的问题——“堆区内容重复释放”,一定要自己提供拷贝构造函数,如下所示:
class Person{
public:
Person(){
cout<<"无参构造函数"<<endl;
mage=0;
};
Person(int age,int height){
cout<<"有参构造函数"<<endl;
mage=age;
mheight=new int(height);
};
Person(const Person& p){
cout<<"拷贝构造函数"<<endl;
//如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题。
mage=p.mage;
mheight=p.mheight; //浅拷贝操作
mheight=new int(*p.mheight); //深拷贝操作
}
~Person(){
cout<<"析构函数调用"<<endl;
if(mheight!=NULL) //对堆区申请的数据进行释放
delete mheight;
};
int mage;
int *mheight; //需要在申请空间
};
int main(){
Person p1(18,180);
cout<<p1.mage<<" "<<*p1.mheight<<endl;
Person p2(p1);
cout<<p2.mage<<" "<<*p2.mheight<<endl;
return 0;
}
C++提供了初始化列表语法,用来初始化属性。
语法:构造函数():属性1(值1),属性2(值2),…{}
class Person{
public:
//传统方式初始化
// Person(int a,int b,int c){
// ma=a;
// mb=b;
// mc=c;
// }
//初始化列表方式初始化
Person(int a,int b,int c): ma(a),mb(b),mc(c){}
void printPerson(){
cout<<ma<<endl;
cout<<mb<<endl;
cout<<mc<<endl;
}
private:
int ma;
int mb;
int mc;
};
int main(){
Person p(1,2,3);
p.printPerson();
return 0;
}
C++类中的成员可以是另一个类的对象,我们称该成员为对象成员。
class Phone{
public:
Phone(string name){
mname=name;
cout<<"Phone构造"<<endl;
}
~Phone(){
cout<<"Phone析构"<<endl;
}
string mname;
};
class Person{
public:
Person(string name1,string name2): pname(name1),phone(name2){
cout<<"Person构造"<<endl;
}
~Person(){
cout<<"Person析构"<<endl;
}
void playGame(){
cout<<pname<<"使用"<<phone.mname<<"牌手机"<<endl;
}
string pname;
Phone phone;
};
int main(){
//Phone构造
//Person构造
Person p("张三","三星");
p.playGame();
//Person析构
//Phone析构
return 0;
}
当类中成员是其他类对象时,我们称该成员为对象成员。
构造的顺序是:先调用对象成员的构造,再调用本类构造。
析构的顺序与构造顺序相反。
静态成员分为:
class Person{
public:
static int ma;
private:
static int mb;
};
int Person::ma=10;
int Person::mb=10;
int main(){
Person p1;
p1.ma=100;
cout<<p1.ma<<endl; //100
Person p2;
p2.ma=200;
//p2.mb=300; //私有权限访问不到
cout<<p1.ma<<endl; //200 共享一份数据所以,p1的ma值也会发生改变;
cout<<p2.ma<<endl; //200
cout<<Person::ma<<endl;
//cout<<Person::mb<<endl; //私有权限访问不到
return 0;
}
在C++中,类内的成员变量和成员函数分开存储,只有非经验成员变量才属于类的对象。
空对象占内存为1,C++为每个空对象也分配一个内存空间,用于区分对象占内存的位置。
class Person{
};
int main(){
Person p;
cout<<sizeof(p)<<endl; //结果是1
return 0;
}
class Person{
public:
Person(){
ma=0;
}
//非静态成员变量占对象空间
int ma;
//静态成员变量不占对象空间
static int mb;
//函数不占对象空间,非静态成员函数只会诞生一份函数实例,也就说多个同类型的对象会公用一份代码。
void func(){
cout<<this->ma<<endl;
}
//静态成员函数也不占对象空间
static void funcb(){
}
};
int main(){
cout<<sizeof(Person)<<endl;
return 0;
}
每个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会公用一块代码。那么问题是:这一块代码是如何区分那个对象调用自己的呢?
C++通过提供特殊的对象指针,this指针,解决上述问题,this指针指向被调用的成员函数所属对象。
this指针是隐含每一个非静态成员函数内的一种指针;
this指针不需要定义,直接使用即可;
指针的用途:
非静态成员函数
中返回对象本身,可使用return *this;class Person{
public:
Person(int age){
//当形参和成员变量同名时,可用this指针来区分,this指针指向调用此函数的对象;
this->age=age;
}
Person & PersonAddPerson(Person p){ //返回的是对象的引用。
this->age+=p.age;
//在类的非静态成员函数中返回对象本身,可使用return *this;
return *this;
}
int age;
};
int main(){
Person p1(10);
cout<<p1.age<<endl;
Person p2(10);
//链式编程思想
p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1);
cout<<p2.age<<endl;
return 0;
}
C++中空指针也是可以调用成员函数的,但是也要注意有咩有用到this指针,如果用到this指针,需要加以判断保证代码的健壮性。
常函数:
成员属性声明时加关键字mutable后,在常函数中依然可以修改
友元的目的:就是让一个函数或者类访问另一个类中的私有成员;
友元的关键字为:friend;
友元的三种实现:
class Building{
//告诉编译器goodday全局函数是Building类的好朋友,可以访问类中的私有内容
friend void goodday(Building *building);
public:
Building(){
this->sittingroom="客厅";
this->bedroom="卧室";
}
private:
string sittingroom;
string bedroom;
};
void goodday(Building *building){
cout<<building->sittingroom<<endl;
cout<<building->bedroom<<endl;
}
int main(){
Building building;
goodday(&building);
return 0;
}
class goodDay{
}
class Building{
//告诉编译器goodDay类是Building类的好朋友,可以访问Building类中私有内容。
friend class goodDay;
}
class goodDay{
goodDay();
void visit();
}
class Building{
//告诉编译器goodDay类是Building类的好朋友,可以访问Building类中私有内容。
friend class goodDay::visit();
}
对于内置数据类型,编译器是知道如何进行运算的。
运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
可以使用成员函数做重载Person operator+(Person &p)
也可以使用全局函数做重载Person operator+(Person &p1,Person &p2)
。
class Person{
public:
int ma;
int mb;
//1、成员函数做重载+
// Person operator+(Person &p){
// Person temp;
// temp.ma=this->ma+p.ma;
// temp.mb=this->mb+p.mb;
// return temp;
// }
};
//全局函数做重载+
Person operator+(Person &p1,Person &p2){
Person temp;
temp.ma=p1.ma+p2.ma;
temp.mb=p1.mb+p2.mb;
return temp;
}
//或者+int类型等
Person operator+(Person &p1,int num){
Person temp;
temp.ma=p1.ma+num;
temp.mb=p1.mb+num;
return temp;
}
int main(){
Person p1;
p1.ma=10;
p1.mb=20;
Person p2;
p2.ma=31;
p2.mb=11;
Person p3=p1+p2;
cout<<p3.ma<<" "<<p3.mb<<endl; //分别是41和31
Person p4=p1+40;
cout<<p4.ma<<" "<<p4.mb<<endl;
return 0;
}
成员函数重载本质调用:
Person p3=p1.operator+(p2)
全局函数重载本质调用
Person p3=operator+(p1,p2);
class Person{
public:
int ma=10;
int mb;
//利用成员函数返回函数重载符——不可,因为无法实现cout在左侧
};
//利用全局函数做重载
ostream & operator<<(ostream &cout, Person &p){
cout<<p.ma<<endl;
cout<<p.mb<<endl;
return cout;
}
int main(){
Person p;
p.ma=100;
p.mb=100;
cout<<p<<endl;
return 0;
}
class myInteger{
public:
friend ostream & operator<<(ostream &cout,myInteger myint);
myInteger(){
num=0;
}
//重载前置++
myInteger & operator++(){ //返回引用是为了只对当前数据进行+操作
num+=1;
return *this;
}
//重载后置++
myInteger operator++(int){ //前置递增和后置递增用传入int做区分 且前置递增返回的是值,不是引用,因为temp作为局部变量,使用结束后就会被释放
//先记录结果
myInteger temp=*this;
//做家操作
num+=1;
//返回之前的操作
return temp;
}
private:
int num;
};
ostream & operator<<(ostream &cout,myInteger myint){
cout<<myint.num;
return cout;
}
int main(){
myInteger integer1;
cout<<++(++integer1)<<endl; //是2
cout<<integer1<<endl; //也是2
myInteger integer2;
cout<<integer2++<<endl; //是0
cout<<integer2<<endl; //是1
return 0;
}
C++编译器1至少给一个类添加4个函数:
class person{
public:
person(int age){
mage=new int(age);
}
~person(){
if(mage!=NULL){
delete mage;
mage=NULL;
}
}
person & operator=(person &p){
//应该先判断是否有属性在堆区,如果有先释放干净,然后再深拷贝
if(mage!=NULL){
delete mage;
mage=NULL;
}
//深拷贝
mage=new int(*p.mage);
//返回自身
return *this;
}
int *mage;
};
int main(){
person p(10);
person p2(20);
person p3(30);
cout<<*p.mage<<endl;
cout<<*p2.mage<<endl;
cout<<*p3.mage<<endl;
p3=p2=p;
cout<<*p.mage<<endl;
cout<<*p2.mage<<endl;
cout<<*p3.mage<<endl;
return 0;
}
class Person{
public:
Person(string name,int age){
mname=name;
mage=age;
}
bool operator==(Person &p){
if(this->mname==p.mname && this->mage==p.mage)
return true;
return false;
}
string mname;
int mage;
};
int main(){
Person p1("Tom",18);
Person p2("Tom",18);
if(p1==p2){
cout<<"p1和p2是相等的"<<endl;
}else {
cout<<"p1和p2不相等"<<endl;
}
return 0;
}
class MyPrint{
public:
void operator()(string text){
cout<<text<<endl;
}
};
void printtest(string text){
cout<<text<<endl;
}
class MyAdd{
public:
int operator()(int num1,int num2){
return num1+num2;
}
};
int main(){
MyPrint myPrint;
myPrint("hello world"); //由于使用起来非常类似于函数调用,因此称为仿函数。
printtest("hello world");
MyAdd myadd;
cout<<myadd(10,40)<<endl; //或者写成下面的样式
//匿名对象调用
cout<<MyAdd()(10,40)<<endl;
return 0;
}
继承的好处:可以减少重复的代码;
class A:public B;
A类称为子类或者派生类;
B类称为父类或者基类;
派生类中的成员,包含两大部分:
一类是从基类继承过来的,一类是自己增加的成员;
从基类继承过来的表现其共性,而新增的成员体现了其个性;
继承方式一共有三种:
- 公共继承public
- 保护继承protected
- 私有继承private
class Base{
public:
int ma;
protected:
int mb;
private:
int mc;
}
//公共继承
class Son : public Base{
}
父类中私有成员也被子类继承下去了,只是由编译器给隐藏后访问不到。
当子类与父类出现同名的成员,如果通过子类对象,访问到子类或父类中同名的数据呢?
class Base{
public:
Base(){
ma=100;
}
void func(){
cout<<"Base中的func函数"<<endl;
}
static void func2(){
cout<<"Base中的静态函数func2"<<endl;
}
int ma;
static int mb;
};
class Son : public Base{
public:
Son(){
ma=200;
}
void func(){
cout<<"Son中的func函数"<<endl;
}
static void func2(){
cout<<"Son中的静态函数func2"<<endl;
}
int ma;
static int mb;
};
int main(){
Son s;
cout<<s.ma<<endl; //访问本类(子类)中函数直接访问就可
cout<<s.Base::ma<<endl; //想要访问父类中的同名函数,需要加父类作用域
s.func();
s.Base::func();
cout<<Son::mb<<endl;
cout<<Son::Base::mb<<endl;
Son::func2();
Son::Base::func2();
return 0;
}
class Son : public Base1,public Base2{
}
多继承中如果父类中出现了同名情况,子类使用时候要加作用域;
两个派生类继承同一个基类,又有某个类同时继承这两个派生类,这种继承被称为菱形继承。
菱形继承问题:
1、羊继承了动物的数据,驼同样继承了动物的数据,当羊驼使用数据时,就会产生二义性;
2、羊驼继承自动物的数据继承了两份,但这份数据只需要一份就可以了。
利用虚继承可以解决菱形继承问题:
class Animal{
public:
int mage;
};
//继承前加virtual关键字后,变为虚继承
//此时公共的父类Animal称为虚基类
class Sheep : virtual public Animal{};
class Tuo : virtual public Animal{};
class SheepTuo : public Sheep,public Tuo{};
int main(){
SheepTuo st;
st.Sheep::mage=100;
st.Tuo::mage=200;
cout<<st.Sheep::mage<<endl;
cout<<st.Tuo::mage<<endl;
return 0;
}
多态分为两类:
class Animal{
public:
//函数前加virtual关键字,变为虚函数
virtual void speak(){
cout<<"animal speak"<<endl;
}
};
class Dog : public Animal{
public:
void speak(){
cout<<"dog speak"<<endl;
}
};
class Cat : public Animal{
public:
void speak(){
cout<<"cat speak"<<endl;
}
};
void dotalk(Animal &animal){
animal.speak();
}
int main(){
Animal animal;
dotalk(animal);
Dog dog;
dotalk(dog);
Cat cat;
dotalk(cat);
return 0;
}
多态满足条件:
1.有继承关系
2.子类重写父类中的虚函数
多态使用:
父类指针或引用指向子类对象
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可以将虚函数改为纯虚函数。
纯虚函数语法: virtual 返回值类型 函数名 (参数列表)=0;
当类中有了纯虚函数,这个类也称为抽象类,抽象类无法实例化对象。子类必须重写父类中的纯虚函数,否则也属于抽象类。
class Base{
public:
//纯虚函数
virtual void func()=0;
};
class Son : public Base{
public:
//重写方法时依旧要加virtual
virtual void func(){
cout<<"func调用"<<endl;
};
};
int main(){
Base *base=NULL; //抽象类无法实例化对象
base=new Son();
base->func();
return 0;
}
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用子类的析构代码。
解决方式:将父类中的析构函数改为虚析构或者纯虚析构。
虚析构和纯虚析构共性:
- 可以解决父类指针释放子类对象
- 都需要有具体的函数实现
虚析构和纯虚析构区别:
- 如果是纯虚析构,该类属于抽象类,无法实现实例化对象。
class Animal{
public:
Animal(){
cout<<"Animal构造函数"<<endl;
}
virtual void speak()=0;
virtual ~Animal(){
cout<<"Animal析构函数"<<endl;
}
};
class Cat: public Animal{
public:
Cat(string name){
catname=new string(name);
}
virtual void speak(){
cout<<*catname<<"小猫在说话"<<endl;
}
~Cat(){
cout<<"Cat析构函数"<<endl;
if(this->catname!=NULL){
delete catname;
catname=NULL;
}
}
string *catname;
};
int main(){
Animal *animal=new Cat("Tom");
animal->speak();
//通过父类指针去释放子类,可能会导致子类对象清理不干净,造成内存泄露
//怎么解决?给父类增加一个虚析构函数
//虚析构函数就是用来解决通过父类指针释放子类对象时,释放不干净的问题!!
delete animal;
return 0;
}
程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放,通过文件可以将数据持久化。
文件类型主要分为两种:
1.文本文件——文件以文本的ASII码形式存储在计算机中;
2.二进制文件——文件以文本的二进制形式存储在计算机中,用于一般不能直接读懂它们。
操作文件的三大类:
1.ofstream:写操作
2.ifstream:读操作
3.fstream:读写操作
文件打开方式:
class Person{
public:
char mname[64];
char mage;
};
void test(){
ofstream ofs;
ofs.open("person.txt",ios::out | ios::binary);
Person p={"张三",18};
ofs.write((const char *)&p,sizeof(p));
ofs.close();
}
int main(){
test();
return 0;
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。