1、内存四区
代码区、全局区、栈区、堆区。
2、引用
1)基本语法
数据类型 &别名=原名
int a;
int &b=a;
b=20;
cout<<a<<endl
cout<<b<<endl
//程序输出
//20
//20
b=100;
cout<<a<<endl
cout<<b<<endl
//程序输出
//100
//100
2)注意
注意引用在创建时必须直接初始化`,定义之后引用不能再指向其他变量。
3)引用做函数参数
使用形式: int ADD(&a,&b)
使用变量作为形参时,函数外变量在函数内被更改后,函数运行结束变量变回原值,但是引用和指针会保留更改数据至变量,且引用的数据传递速度比指针快,节省资源。
4)引用做函数返回值
1、不要返回局部变量的引用,函数结束后相关内存就没了
2、函数的调用可以作为左值
int& retu()
{
staic int a=10;//全局区 程序执行完成后释放
return a;
}
funtion ()=100;???函数作为左值了
当函数的返回为引用时,相当于将返回的函数值,即返回的引用作为左值。
retu()=1000;即a=100;
5)引用的本质
引用本质在C++本质是个指针常量
int& ref=a; int* const ref=&a;//指向不可变 即不可改变指向的变量
ref=20;//ref为引用,内部转化为*ref=20;
6)常量引用
用来修饰形参,防止误操作。
说明:
int &ref=10;//本句不成立 引用对象必须为左值
const int &ref=10;//成立
使用场景:
void ShowValue(int &val)
{
val=1000;//会导致外部变量被改变
cout<<"val="<<val<<endl;
}
void ShowValue(const int &val)
//此处编译器自行创建一个变量用于引用
{
//val=1000;//本句报错
cout<<"val="<<val<<endl;
}
3、函数
函数的占位参数
void func(int a,char)
{}
char就是占位参数,使用时占位参数需要输入即func(12,5);正确。
占位参数也可以有默认数值
void func(int a,char='c')
{}
函数重载
拥有相同的函数名,函数的参数类型不同或个数和顺序不同。函数的返回值不可以作为依据。
注意:
引用作为重载条件
void fun(int& a)
{cout<<"1"<<endl;}
void fun(const int& a)
{cout<<"2"<<endl;} //以上属于可重载
以上函数参数输入时,若输入一个变量,由于变量默认可读可写,因此调用int&a,输入常量则调用const int &a
函数重载遇到默认参数
void fun(int a,int b=10)
{cout<<"1"<<endl;}
void fun( int a)
{cout<<"2"<<endl;} //这个组合是有问题的
4、类和对象
面向对象的三个特性:封装、继承、多态。
4.1 封装
~将属性和行为作为一个整体变现事物
~将属性和行为加权限控制
class Circle
{
//访问 权限
public:
//属性
int M_r;
//行为
double Cir(return 2*3.14*M_r;)
};
int main(void)
{
Circle C1;
C1.M_r=10;
cout<<C1.Cir();
}
访问权限
public 公共权限 //成员类内类外都可以访问
private 私有权限 //类内可以访问,类外不可以访问 (继承有不同, 父类中的私有部分子类可以访问)
protected 保护权限 //只有类内可以访问,类外和子类不可以访问
class people
{
public:
string M_name;
private:
string M_car;
protected:
int M_password;
};
struct和class的区别
都可以定义类,唯一区别在于默认访问权限
struct默认公共 class默认私有
class people
{
(private:)
string M_car;
};
struct people
{
(public:)
string M_car;
}
成员属性私有的优点
1、可以自己控制读写权限
2、对于写可以检测数据有效性
4.2 对象的初始化和清理
4.2.1 构造函数和析构函数(写在public下)
构造函数:初始化——创建对象时为对象的成员属性的赋值。
析构函数:清理
这两个函数有编译器自动调用,且不写这两个函数,系统将提供两个空函数。
构造函数语法:
类名(){}
1、构造函数没有返回值也不写void
2、函数名称和类名相同
3、构造函数可以有参数,因此可以重载
4、程序在调用对象时会自动调用且只会调用一次
析构函数语法:
~类名(){}
1、构造函数没有返回值也不写void
2、函数名称和类名相同,在名称前加~
3、构造函数没有参数,因此不可以重载
4、程序在对象销毁前会自动调用且只有一次。
4.2.2 构造函数的分类和调用
1、无参构造(默认构造)和有参构造
2、拷贝构造函数
class person
{
public:
person()
{
cout<<"无参构造"<<endl;
}
person(int a)
{
age=a;
cout<"有参构造"<<endl;
}
person (const person &p)//用引用的方式调用并加const防止更改原类参数
{
//将传入的对象所有属性拷贝出
age=p.age;
cout<"拷贝构造"<<endl;
}
};
调用
1、括号法 *
person P1;//(默认构造函数的调用)
person P2(10);//有参构造函数
person P3(P2);//拷贝构造函数
注意事项:调用默认构造函数时,不要加“()”,person P1();会被系统看做函数声明!
2、显示法
person P1; //无参构造
person P2;=person (10);//有参构造
person P3;=person (P2);//拷贝构造
person (10); //创建了匿名对象 放在等号后将作为类似数据,单独作为一行时,该匿名对象只在当前行有效。
注意事项2:不要利用拷贝构造函数初始化匿名对象
person (P3);//有重定义的语法错误,person (P3)等价于person P3; 定义了P3两次,被视为对象P3的成名。
3、隐式转换法
person (P4)=10;//相当于person P4= person (10); //有参构造
person (P5)=P4;//拷贝构造
4.2.3 拷贝构造函数调用时机
1.使用一个已经创建完毕的对象来初始化一个新对象
2.值传递的方式个函数参数传值
3.以值的方式返回局部对象
void text01()
{
person P1(20);//有参构造函数
person P2(P1);//拷贝构造函数的第一种情况
}
/*---------------------------*/
void dowork(person p_wo)
{
}
void text02()
{
person p;//默认构造函数
dowork(p);//拷贝构造函数 实参在调用形参的时候使用拷贝构造函数
}
/*--------------------------*/
person dowork_02(person p_wo)
{
person P1;//默认构造函数
return P1;//不会返回P1 会拷贝P1创建新变量并返回
}
void text03()
{
person P=dowork_02();//person P和dowork_02中的person P1地址不相同
}
4.2.4 构造函数的调用规则
默认情况下C++编译器至少给一个类添加3个函数
1.默认构造函数(无参,函数体为空 )
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝 这个函数体不为空,且拷贝执行后会调用析构函数
●如果用户定义有参构造函数,C++不在提供默认无参构造,但是提供默认拷贝构造。
●如果用户定义拷贝构造函数,C++不会再提供其他构造函数
创建有参构造后不创建无参构造,若使用无参构造则会报错!只创建拷贝构造同理。
4.2.5 深拷贝和浅拷贝
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作。
浅拷贝会带来堆区被重复释放的问题。解决方式是深拷贝。
示例:
class person
{
public:
person()
{
cout<<"无参构造"<<endl;
}
person(int a)
{
cout<<"有参构造"<<endl;
M_a=a;
}
person(int a,int hei)
{
cout<<"有参构造"<<endl;
M_a=a;
M_hei=new int(hei);
}
~person()
{
if(M_hei!=NULL)//堆区数据释放
{
delete M_hei;
M_hei=NULL;
}
cout<<"析构"<<endl;
}
person(const person &p)//深拷贝
{
cout<<"深拷贝"<<endl;
M_a=p.M_a;
// M_hei=p.M_hei;//浅拷贝
M_hei=new int (*p.M_hei); //深拷贝
}
M_a;
M_hei;
};
void text01()
{
person p;
person P1(P);//浅拷贝
}
4.2.6 初始化列表
语法:构造函数():属性1(值1),属性2(值2),属性3(值3){}
构造函数(参数1,参数2,参数3):属性1(参数1),属性2(参数2),属性3(参数3){}
//传统构造
person(int a,int hei)
{
M_a=a;
M_hei=hei;
}
//列表初始化属性1 固定值
person():M_a(10),M_hei(12)
{
cout<<"列表初始化属性1"<endl;
}
//列表初始化属性2
person(int a,int hei):M_a(a),M_hei(hei)
{
cout<<"列表初始化属性2"<endl;
}
void text01()
{
person(10,12);
person P;//本句调用列表初始化1
person P(20,12);本句就调用列表初始化2
}
4.2.7 类对象作为类成员
C++的类成员可以是另一个类的对象,乘该成员为对象成员
class A{};
class B
{
A a;
};
B 类中油对象A作为成员 A作为对象成员
问题:创建B对象时,A和B的构造和析构谁先谁后
构造: A 先 B后 //Phone 先 Person后
析构: B先 A后 // Person先 Phone后
class Phone
{
public:
string M_Pname;
Phone(string Pname)
{
cout<<"Phone构造"<<endl;
M_Pname=Pname;
}
~Phone()
{
cout<<"Phone析构"<<endl;
}
};
class Person
{
public:
//姓名
string M_name;
//手机
Phone M_Phone;
//隐式转换法
//Phone M_Pname = Pname
Person (string name,string Pname):M_name(name),M_Pname(Pname)
{
cout<<"Person构造"<<endl;
}
~Person()
{
cout<<"Person析构"<<endl;
}
};
//构造: Phone 先 Person后
//析构: Person先 Phone后
void text01()
{
Person P("张三","HUA");
cout<<P.M_name<<"拿着"<<P.M_Pname<<"手机"<<endl;
}
4.2.8 静态成员
静态成员就是在成员变量和成员函数前加上关键字static,成为静态成员
静态成员分为
●静态成员变量
所有对象共享同一份数据
在编译阶段分配内存
类内声明,类外初始化
●静态成员函数 有访问权限的,public和private有效
所有对象共享一个函数
静态成员函数只能访问静态成员变量
实例:
class person
{
public:
static void func()
{
M_A=100;
//M_B=90;//报错!只能访问静态变量
cout<<"Static void"<<endl;
}
static int M_A;
int M_B;
};
int person::M_A=0;
void test01()
{
//1、通过对象访问
person p;
p.func();
//2.通过类名
person::func();
}
4.3 C++对象模型和this指针
4.3.1成员变量和成员函数分开储存
C++中类的成员函数和成员变量分开存储
只有非静态成员变量才属于类的对象上
//成员函数和成员变量是分开存储的
class person1
{
};
void test01()
{
person1 p
cout<<"size of p"<<sizeof(p)<<endl;//若person为空 则=1;C++编译器会为每个空对象分配一个字节空间,为区分空对象占内存的位置。
}
class person
{
int M_a;
static int M_b;
void function()
static void function2()
};
int person::M_b;
void test02()
{
person p
cout<<"size of p"<<sizeof(p)<<endl;//若person不为空,则成员变量有多大就多大,只有int则为 sizeof(int)=4;内有静态变量或静态非静态函数时内存大小不考虑静态变量。
}
4.3.2 this指针概念
由于每个非静态成员函数只会产生一个函数实例,也就是说多个同类型对象会共用一块代码,那么这块代码如何区分是哪个对象调用的自己呢。
通过this指针,this指针指向被调用的成员函数所属的对象。即P1调用 this指向P1,P2调用指向P2。
用途:
●当形参和成员变量同名时,可用this指针区分
●在类的非静态成员函数中返回对象本身,可使用return *this
class person
{
public :
person (int age)
{
// age=age;//这样赋值会导致无法对成员变量赋值
this->age=age;//test01中this指向对象P1
}
void personadd(person &p) //一句运算只能调用一次
{
this->age+=p.age;
}
person& personaddmo(person &p)//返回一个本体需要用引用返回,用值返回,会在return行拷贝一个person对象返回后析构,导致多次累积时前面的函数执行无效。
{
this->age+=p.age;
return *this;
}
int aga;
};
void test01()
{
person P1(18);
cout<<"年龄是"<<P1.age<<endl;
}
void test02()
{
person P2(18);
person P3(18);
P2.personadd(P3);
cout<<"年龄是"<<P2.age<<endl;//36
P2.personadd(P3).personadd(P3).personadd(P3);//链式编程
cout<<"年龄是"<<P2.age<<endl;//54
}
4.3.3 空指针访问成员函数
class person
{
public :
void showClassname()
{
cout<<"this is Persong class"<<endl;
}
void showpersonname()
{
if(this==NULL){return ;} //防止传入空指针
cout<<"age = "<<M_age<<endl;
//默认 cout<<"age = "<<this->M_age<<endl;
}
int M_age;
};
void test01()
{
person*P=NULL; //空指针
P->showClassname();
// P->showpersonname();//错误!由于P为空指针,若访问空指针内的成员变量会导致报错。
}
4.3.4 const 修饰成员函数
const限定只读属性
常函数:
●成员函数后加const后我们称这个函数为常函数
●常函数内不可以修改成员属性
●成员属性声明时加关键字mutable后,在常函数中依然可以修改
常对象:
●声明对象前加const称该对象为常对象
●常对象只能调用常函数
class person
{
public :
void personvoid()
{}
//void showperson() 相当于person *const this,this的指向不可以改变,只能指向本次调用函数的对象。
//void showperson() const 相当于const person *const this,this的指向不可以改变,只能指向本次调用函数的对象,this指针指向值也不可改变。
//在成员函数后加const,修饰的是this指针,让指针指向的值也不可以被修改。
void showperson() const //函数体内不可做成员属性修改
{
cout<<"age = "<<M_A<<endl;
}
int M_A;
mutable int M_B;//特殊变量,及时在常函数中也可以改变这个值,加关键字mutable。
};
void test02()
{
const person p;//对象前加const变为常对象
// p.M_A=100;//报错,常对象的值不可以被修改
p.M_B=100;//加 mutable后,常对象中也可以修改
// p.personvoid();错误,由于非常函数中有可能改变属性,常对象不允许修改属性,所以常对象只允许调用常函数
}
4.4 友元
友元的目的就是让一个函数或者类访问另一个类中私有成员。
关键字 friend
友元用于类外函数访问类的私有空间
友元三种
1 友元函数
2 友元类
3 友元成员函数
通过让函数成为类的友元可以是该函数与类的成员函数相同的访问权限
友元函数创建即在原型声明前加关键字 friend:
friend Time operator*(double m,const Rime &t);
函数定义,即函数体还是与普通函数相同,不能使用成员函数中的::限定符
//友元需要在类中先声明,friend关键字不能出现在函数体中,需要在此处声明加。本程序使用友元是因为再次重载+运算符需要与之前+重载不同个数的函数参数,友元不受原类成员函数的限制
H:
class my
{
private :
int num;
...
public :
...
my operator+(const my&m)const;
my operator+(int X);
friend my operator+(int X,my &m); //友元声明
};
cpp:
my my::operator+(const my &m)const
{
my re;
...
return re;
}
my my::operator+(int X)
{
my re;
...
return re;
}
my operator+(int X,my &m)
{
my re;
...
return re;
}
4.4.1 全局函数做友元
class building
{
frined void goodgay(building *build);//全局函数友元
public:
building()
{
M_sittingRoom="客厅";
M_bedRoom="卧室";
}
string M_sittingRoom;//客厅
private:
string M_bedRoom;//卧室
}
void goodgay(building *build)
{
cout〈〈"好基友全局函数正在访问:"<<build->M_sittingRoom<<endl;
cout〈〈"好基友全局函数正在访问:"<<build->M_bedRoom<<endl;
}
void test01()
{
}
4.4.2 类做友元
class building
{
friend class goodgay; //goodgay是building的友元,可访问私有成员
public:
building();
string M_sittingRoom;//客厅
private:
string M_bedRoom;//卧室
}
building::building()//building::表示是building下的成员函数
{
M_sittingRoom="客厅";
M_bedRoom="卧室";
}
class goodgay
{
public:
goodgay();
void visit();//参观函数 访问building的公有和私有部分
building *build;
}
goodgay::goodgay()
{
//创建建筑物对象
build=new building;
}
goodgay::visit()
{
cout〈<"好基友类正在访问:"<<build->M_sittingRoom<<endl;
cout〈<"好基友类正在访问:"<<build->M_bedRoom<<endl;//不加友元会报错
}
void test01()
{
GoodGay gg;
gg.visit();
}
4.4.3 成员函数做友元
class goodgay
{
//告诉编译器GoodGay类下的visit成员函数作为本类的好朋友,可以访问私有成员
friend void goodGay::visit();
public:
goodgay();
void visit();//可以访问其他类中的私有成员
void visit2();//普通函数,无法访问其他类的私有成员
building *build;
}
goodgay::goodgay()
{
//创建建筑物对象
build=new building;
}
goodgay::visit() //访问私有空间
{
cout〈<"visit正在访问:"<<build->M_bedRoom<<endl;//不加友元会报错
}
goodgay::visit2() //访问公共空间
{
cout〈<"好基友类正在访问:"<<build->M_sittingRoom<<endl;
}
class building
{
public:
building();
string M_sittingRoom;//客厅
private:
string M_bedRoom;//卧室
}
building::building()//building::表示是building下的成员函数
{
M_sittingRoom="客厅";
M_bedRoom="卧室";
}
void test01()
{
GoodGay gg;
gg.visit();
gg.visit2();
}
4.5 运算重载
operator关键字
4.5.1 加号运算符重载
class person
{
public:
int M_A;
int M_B;
};
//成员函数
person personaddperson(person &p)
{
person temp;
temp.M_A=p.M_A+M_A;
temp.M_B=p.M_B+M_B;
return temp;
}
person operator+ (person &p)
{
person temp;
temp.M_A=p.M_A+M_A;
temp.M_B=p.M_B+M_B;
return temp;
}
void test01()
{
person P1;
P1.M_A=11;
P1.M_B=12;
person P2;
P2.M_A=21;
P2.M_B=22;
person P3;
P3=P2.personaddperson(P1);//成员函数运算符重载的本质
P3=P2+P1;//
}
//全局函数重载
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;
}
//P3=personaddperson(P1,P2);//全局函数运算符重载的本质
运算符重载也可以函数重载
person operator+ (person &p1,int num)
{
person temp;
temp.M_A=p1.M_A+num;
temp.M_B=p1.M_B+num;
return temp;
}
//P3=P1+20;就可行了
person operator+ (int num,person &p1)
{
person temp;
temp.M_A=p1.M_A+num;
temp.M_B=p1.M_B+num;
return temp;
}
//P3=20+P1;就可行了
4.5.2 左移运算符重载
可以输出自定义的数据类型( cout 打印)
一般不会用成员函数实现<<的重载,因为这样只能把 cout 放在<<右面,这样就需要访问私有属性时将重载函数加入友元
。
想得到cout<<P 回溯得到函数声明的本质为
operator<<(cout ,p),cout属于ostream类,p为自定义类,那么如下
ostream operator<<(ostream &cout,person &P)
{
cout<<“第”<<t.num;
return os;
} //cout可以使用任意名字
连续输出的链式编程:(链式编程函数的返回值一定不会是void)
//第二个友元 对象作为cout可直接输出 本方式允许cout<<T;但是不允许cout<<T<<"xxxx";
void operator<<(ostream &os,const my&t)
{
os<<"第"<<t.num;
}
//第三个友元
/*
若可实现cout<<T<<T2
cout<<T<<T2=(cout<<T)<<T2;
<<要求<<左边是一个ostream对象,则将(cout<<T)变成一个对象即可,即返回一个 ostream引用
*/
ostream & operator<<(ostream &os,const my&t)
{
os<<"第"<<t.num;
return os;
}
//使用时
void main()
{
my cl1;
my cl2;
...
cout<<cl1<<cl2<<endl;
...
}
4.5.3 递增运算符
递增运算符++
++A先加再输出 A++先输出再加
//重载++运算符 分为前置和后置运算符
//重置前置++运算符
class myint
{
public:
friend myint& operator++();
private:
M_int;
}
myint& operator++()//此处不使用引用时,++(++a)将只能加1不能加2
{
++M_int;
return *this;
}
//重置后置++运算符
myint operator++(int)//int为占位参数,用于区分前置后置,这个位置决不可返回引用,因为函数运行完后原内存被释放
{
myint temp=*this;
++MM_int;
return temp;
}
4.5.4 赋值运算符重载
C++编译器至少给一个类添加四个函数
构造析构拷贝构造还有就是赋值运算符operator=,对其属性进行值拷贝
编译器自带的赋值运算符是将前者的数据完全复制到后者中,若指向某地址,则两者均指向该地址,即自带的赋值运算是浅拷贝,这一点在析构函数中有释放内存操作时可体现出。
class person
{
person(int age)
{
M_Age=new int(age);//在堆区创建
}
~person()
{
if(M_Age!=NULL)
{
delete M_Age;
M_Age=NULL;
}
}
int *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;//this是个指针,若要返回值则要解引用
}
4.5.5 关系运算符
4.5.6 函数调用运算重载
●函数调用运算符小括号()也可以重载
●由于重载后的使用方法非常像函数调用,因此成为仿函数
●仿函数没有固定写法,非常灵活
class MYPrint
{
public:
void operator(string text)
{
cout<<text<<endl;
}
}
void test01()
{
MYPrint MY;
MY("hello word");//由于使用时很像函数调用,因此称为仿函数
}
class MYadd
{
public:
int operator(int a,int b)
{
return a+b;
}
}
void tast02()
{
MYadd add;
int res=add(100,200);
//匿名函数对象 本句为匿名对象
cout<<MYadd()(100,200)<<endl;//输出 300
}
4.6 继承
继承是面向对象的三大特性之一
定义类时,下级别的成员除了拥有上一级的共性,还有自己的特性,可以考虑继承的技术减少重复代码。
4.6.1 继承的基本语法
语法:class 子类:继承方法 父类
class CPP:public BasePage //继承
子类 也称为 派生类
父类 也称为 基类
示例
class Java
{
public:
...//重复部分
void content()
{
cout<<"Java视频"<<endl;
}
};
class Python
{
public:
...//重复部分
void content()
{
cout<<"Python视频"<<endl;
}
};
void test01()
{
Java J1;
Python PY;
cout<<"JAVA页面"<<endl;
...
J1.content();
cout<<"Python页面"<<endl;
...
PY.content();
}
//继承书写
class BasePage
{
public:
...//重复部分
};
class CPP:public BasePage //继承
{
public:
void content()
{
cout<<"C++视频"<<endl;
}
};
4.6.2 继承方式
继承语法:class 子类:继承方法 父类
继承方式一共有三种:
●公共继承 (public)
●保护继承 (protected)
●私有继承 (private)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B1OKjeoM-1576129664068)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1570676533768.png)]
4.6.3 继承中的对象模型
//父类中所有非静态成员属性都会被子类继承下去
//父类中私有成员属性是被编译器给隐藏了,因此是访问不到,但是确实被继承下去了
4.6.4 继承中的构造和析构
子类继承父类时,父类需要新建对象,构造和析构的顺序是:
父类构造->子类构造->子类析构->父类析构
4.6.5 继承同名成员处理方式
问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据呢?
●访问子类同名成员 直接访问即可
●访问父类同名成员 需要加作用域 (父类名::成员)
如果子类中出现了与父类同名的成员函数,父类的所有同名函数包括重载函数都会被隐藏,想访问父类的成员函数需要加作用域。
class Base
{
public:
Base()
{
M_A=100;
}
void func()//同名的成员函数
{
cout<<"Base-func"<<endl;
}
void func(int a)//同名的成员函数
{
cout<<"Base-func-int"<<endl;
}
int M_A; //同名的成员属性
};
class Son:public Base
{
public:
Son()
{
M_A=200;
}
void func()//同名的成员函数
{
cout<<"son-func"<<endl;
}
int M_A;
}
void test01()
{
Son s;
cout<<"Son M_A="<<s.M_A<<endl;//200
cout<<"Base M_A="<<s.Base::M_A<<endl;//100,加一个父类的作用域
s.func();//"son-func"
s.Base::func();//"Base-func"
//s.func(10);//这样会报错.如果子类中出现了与父类同名的成员函数,父类的所有同名函数包括重载函数都会被隐藏,想访问父类的成员函数需要加作用域。
s.Base::func(100);"Base-func-int"
}
4.6.6 继承中同名静态成员处理
静态成员同名处理规则与非静态成员完全一致,静态变量有两种访问方式。
1、通过对象访问:创建对象 对象.成员变量/函数
2、通过类名 访问:
子类名::成员变量/函数
子类名::父类名::成员变量/函数 第一个::表示通过类名方式访问 第二个::表示访问父类作用域下
4.6.7 多继承语句
C++允许一个类继承多个类
语法:class 子类:继承方式父类1,继承方式父类2…
多继承可能会引发父类中有同名成员出现,需要加作用域区分
C++实际开发中不建议用多继承
多个父类时,若父类之间有重复名字,则需要加作用域区分,否则报错
4.6.8 菱形继承
菱形继承概念:
两个派生类继承同一个基类
又有某个类同时继承着两个派生类
这种继承被称为菱形继承,或者钻石继承
当出现菱形继承时,若第三层从第二层继承了两个同名成员变量,则有俩个同名不同作用域的变量。造成资源浪费。
利用虚继承解决,虚承权加virtual关键字。
父类前全加virtual,子类继承的是vbtr(虚基类指针)。指针指向虚基类表
class sheep:virtual public Animal{};
class Tuo:virtual public Animal{};
class SheepTuo :public Sheep,public Tuo{};
//Sheep::m Age
cout<< st.Sheep::m Age="<< st.sheep::m Age<<endl;
//Tuo::m_Age
cout<<"st.Tuo::m_Age="<< st.Tuo::m_Age<<endl;
//Tuo::m_Age
cout<<"st.mAge="<< st.m_Age<<endl;
4.7 多态
多态是C++面向对象的三大特性之一
多态分为两类:
●静态多态:函数重载和运算符重载属于多态,复用函数名
●动态多态:派生类和虚函数实现运行时多态
静态和动态区别:
●静态多态的函数地址早绑定-编译阶段确定函数地址
●动态多态的函数地址晚绑定-运行阶段确定函数地址
4.7.1 基本语法
父类内的函数返回类型前加virtual 实现晚绑定
class Animal
{
public:
void speak()
{cout<<"动物"<<endl;}
};
class Cat:public Animal
{
public:
void speak()
{cout<<"猫"<<endl;}
};
//属于地址早绑定 编译阶段确定了函数的地址 始终指向Animal
void doSpeak(Animal & animal)
{
animal.speak();
}
void test01()
{
Cat cat;
doSpeak(cat);//显示动物 地址早绑定
}
//地址晚绑定
class Animal
{
public:
virtual void speak() //实现晚绑定
{cout<<"动物"<<endl;}
};
void test01()
{
Cat cat;
doSpeak(cat);//显示猫 地址晚绑定
}
动态多态的满足条件
1、有继承关系
2、子类要重写父类的虚函数(speak)
重写:函数返回值类型 函数名 参数列表完全相同
动态多态的使用:
父类的指针或者引用 执行子类对象
4.7.2 多态的原理分析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JkSZdVAU-1576129664070)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1570772163705.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dwHDVXqd-1576129664071)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1570772336327.png)]
4.7.3 多态案例一 计算器类
多态优点:
●代码组织结构清晰
●可读性强
●利于前期和后期的扩展及维护
扩展新功能提倡开闭原则:对扩展进行开发,对修改进行关闭
代码组织结构清晰:功能块分区,修改明确
可读性强:功能区分开
//实现计算器抽象类
class AbstractCalculator
{
public:
virtual int getResult() {return 0;}
int m_Numl;
int m_Num2;
}
//加法运算
class ADD:public AbstractCalculator
{
public :
int getResult() {return m_Numl+m_Num2;}
}
//减法运算
class SUB:public AbstractCalculator
{
public :
int getResult() {return m_Numl-m_Num2;}
}
//乘法运算
class MUL:public AbstractCalculator
{
public :
int getResult() {return m_Numl*m_Num2;}
}
void test02()
{
//多态使用条件
//父类指针或引用指向子类对象
//加法运算
AbstractCalculator *abc=new ADD;
abc->m_Num1=10;
abc->m_Num2=10;
cout<<abc->getResult()<<endl;//10+10=20
delete abc;
//减法
abc=new MUL;
cout<<abc->getResult()<<endl;//10+10=0
delete abc;
}
4.7.4 纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容因此可以将虚函数改为纯虚函数纯虚函数
语法:virtual 返回值类型函数名(参数列表)=0;
virtual void func(int a )=0;
当类中只要有一个纯虚函数,这个类也称为抽象类
抽象类特点:
●无法实例化对象(不能定义对象)。
●子类必须重写抽象类中的纯虚函数,否则也属于抽象类(子类中没有对父类中的虚函数重写,则子类也被视为抽象类)。
4.7.5虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方式:将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:
●可以解决父类指针释放子类对象刻
●都需要有具体的函数实现
虚析构和纯虚析构区别:
●如果是纯虚析构,该类属于抽象类,无法实例化对象
class Animal
{
public:
virtual void speak()=0;
Animal()
{
}
virtual ~Animal()
{
}
}
class Cat:public Animal
{
public:
Cat(string name)
{
M_name=new string(name);
}
void speak()
{
cout<<"小猫"<<endl;
}
~Cat()
{ //不走?
cout<<"cat析构"<<endl;
if(M_name!=NULL)
{delete M_name;}
}
string *M_name;//创建在堆区
}
void test01()
{
Animal * animal=new Cat("Tom");
animal->speak();
//父类指针在析构时不会走子类的析构函数,导致需要在子类释放的堆区内存无法释放
delete animal;
}
将父类的析构函数前加virtual 成为虚析构,解决父类指针释放子类对象时子类析构函数中释放内存不执行的问题
//父类
virtual ~Animal()
{
}
//子类
~Cat()
{
cout<<"cat析构"<<endl;
if(M_name!=NULL)
{delete M_name;}
}
纯虚析构:
virtual ~Animal()=0;//只有本句会报错要求析构函数有代码实现
//类声明之外加
Animal::~Animal()
{}
虚析构和纯虚析构只有存在正常无法释放的内存时才需要(例如:子类成员变量有指针),正常使用可以不用。
总结:
1.虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
2.如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
3.拥有纯虚析构函数的类也属于抽象类
5 文件操作
C++中对文件操作需要包含头文件
文件类型:
1、文本类-以ASCII码形式储存
2、二级制文件-以文本的二进制形式存储
操作文件的三大类:
1.ofstream:写操作
2.ifstream:读操作
3.fstream:读写操作
5.1 文本文件
5.1.1 写文件
写文件步骤如下:
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 二进制方式 |
总结:
●文件操作必须包含头文件fstream
●读文件可以利用ofstream,或者fstream类
●打开文件时候需要脂定操作文件的路径,以及打开方式·利用<<可以向文件中写数据
●操作完毕,要关闭文件
5.1.2 读文件
判断文件是否打开了:
ifstream ifs;
if(ifs::is_open()) //bool类型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wuUUab7N-1576129664073)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1570780444772.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tpekB2rx-1576129664075)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1570780546674.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tTtIjZjM-1576129664076)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1570780607683.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZTy7QaQY-1576129664078)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1570780638234.png)]
//EOF end of file
总结:
●读文件可以利用ifstream,或者fstream类
●利用is_open函数可以判断文件是否打开成功
●close关闭文件
5.2 二进制文件
以二进制的方式对文件进行读写操作
打开方式要指定为ios:binary
5.2.1 写文件
二进制方式写文件主要利用流对象调用成员函数write
函数原型:ostream& write(const char *buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HfMBgnvU-1576129664080)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1570780903993.png)]
5.2.2读文件
二进制方式读文件主要利用流对象调用成员函数read
函数原型:istream& read(char *buffer,int 1en);
参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YqEOg7Tm-1576129664085)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1570781025683.png)]