目录
学习内容:
1.运算符重载
1.1 运算符种类
单、算、移、关、逻辑、条件表达式、赋值、逗号
1.2 运算符重载函数
1> 函数名:统一为 operator# //这里的#表示要被重载的运算符
2> 参数:根据运算符的功能和位置而定,全局函数版双目运算符参数有两个,成员函数版双目运算符参数一个
全局函数版,单目运算符参数有一个,成员函数版单目运算符参数无
3> 返回值:根据运算符的特征而定
1.3 调用原则和调用机制
调用时机:当对象使用该运算符时,系统自动调用运算符重载函数
调用机制:左调右参原则,运算符左侧是函数的调用者,运算符右侧是函数的参数
例如:a = b; //a.operator=(b);
s1 + s2; //s1.operator+(s2);
1.4 运算符重载函数的格式
1> 任意一个运算符重载都有两个版本:成员函数版和全局函数版
2> 实际使用过程中,只实现一种重载的情况,常用成员函数版
1.5 算术运算符
1> 种类:+、-、*、/、%
2> 算术运算符属于双目运算符,格式:L # R; L表示左操作数,#表示运算符号,R表示右操作数
3> 左操作(L):既可以是左值也可以是右值
右操作数(R):既可以是左值也可以是右值
运算结果:只能是右值
4> 格式:
成员函数版:const 类名 operator#(const 类名 &R) const
解析:第一个const保护运算结果不被修改
第二个const保护右操作数在运算过程中不被修改
第三个const保护左操作数自身在运算过程中不被修改
全局函数版:const 类名 operator#(const 类名 &L,const 类名 &R)
1.6 赋值类运算符重载
1> 种类:=、+=、-=、*=、/=、%=
2> 赋值运算符属于双目运算符,格式:L # R; L表示左操作数,#表示运算符号,R表示右操作数
3> 左操作(L):只能是左值
右操作数(R):既可以是左值也可以是右值
运算结果:左操作数自身的引用
4> 格式:
成员函数版:类名 & operator#(const 类名 &R)
全局函数版:类名 & operator#( 类名 &L,const 类名 &R)
1.7 关系运算符重载函数
1> 种类:>、=、
2> 关系运算符属于双目运算符,格式:L # R; L表示左操作数,#表示运算符号,R表示右操作数
3> 左操作(L):既可以是左值也可以是右值
右操作数(R):既可以是左值也可以是右值
运算结果:bool类型的右值
4> 格式:
成员函数版:const bool operator#(const 类名 &R) const
全局函数版:const bool operator#(const 类名 &L,const 类名 &R)
1.8 单目运算符
1> 种类:-(负号) 、!
2> 格式:# R; #表示运算符号,R表示右操作数
3> 操作数(R):既可以是左值也可以是右值
运算结果:同类的一个右值
4> 格式:
成员函数版:const 类名 operator#() const
全局函数版:const bool operator#(const 类名 &R)
1.9 自增自减运算
1> 种类:++ 、 --
2> 格式:# R; #表示运算符号,R表示右操作数
3> 操作数(R):只能是左值
运算结果:
前置:是自身的引用
成员格式:类名 &operator#()
全局格式:类名 &operator#(类名 &R)
后置:临时值
成员格式:类名 operator#(int)
全局格式:类名 operator#(类名 &R, int)
1.10 插入和提取运算符重载
1> 插入和提取运算符的来源
namespace std {
ostream cout;
istream cin;
}
2> 由于运算符重载函数的调用遵循左调有参原则,所以,自定义类的重载函数,需要流对象和自定义类的对象
例如:cout<<c //cout.operator<<(c);
3> 此时,原则上来说,需要在流所在的类对象中,写成员函数,难度较大,所以,对于该类的重载函数,我们只能实现全局函数版,将该函数作为类的友元函数
4> 格式:
ostream& operator
istream& operator>>(istream &L, 类名 &R)
1.11 类型转换运算符
1> 有时,需要将类对象进行强制类型转换为其他数据类型,此时就需要在类体内提供类型转换运算符重载函数
2> 定义格式: operator 要转换的类型() { 返回对应类型的数据 }
1.12 函数对象(仿函数)
1> 本质上就是重载 () 运算符,由于调用时,跟函数调用一样,所以称为仿函数
2> 定义格式: operator() (参数列表) {函数体内容}
3> 调用格式:对象名(实参名); //对象 . operator() (实参名)
1.13 运算符重载的限制
1> 运算符重载只能在已有的运算符基础上进行重载,不能凭空捏造一个运算符
2> 运算符重载不能更改运算符的本质:如不能在加法运算符重载中实现减法
3> 运算符重载不能改变运算符的优先级
4> 运算符重载不能改变运算符的结合律
5> 运算符重载函数不能设置默认参数
6> 成员函数版的参数要比全局函数版的参数少一个,原因是对象本身就是一个参数
#include <iostream>
using namespace std;
//定义一个复数类: 由实部和虚部组成的一个数据 a + bi
class Complax
{
private:
int real; //实部
int vir; //虚部
public:
Complax() {cout<<"无参构造"<<endl;} //无参构造
Complax(int r, int v):real(r),vir(v) {cout<<"有参构造"<<endl;} //有参构造
~Complax() {cout<<"析构函数"<<endl;} //析构函数
//定义输出函数
void show()
{
if(vir > 0)
{
cout<<real << "+"<<vir<<"i"<<endl;
}else
{
cout<<real << vir<<"i"<<endl;
}
}
//定义成员函数版完成两个复数相加:实部+实部 虚部+虚部
const Complax operator+(const Complax &R)const
{
Complax temp; //无参构造
temp.real = this->real + R.real;
temp.vir = this->vir + R.vir;
return temp;
}
//实现全局函数版,需要将全局函数版设置成友元函数
friend const Complax operator-(const Complax &L, const Complax &R);
//成员函数版完成加等于运算符重载: 实部 += 实部 虚部+=虚部
Complax &operator+=(const Complax &R)
{
this->real += R.real;
this->vir += R.vir;
return *this;
}
//定义成员函数版完成关系运算符重载: 实部>实部 && 虚部>虚部
bool operator>(const Complax &R)const
{
return this->real>R.real && this->vir>R.vir;
}
//定义成员函数版完成负号运算符重载: 实部 = -实部 虚部=-虚部
const Complax operator-()const
{
Complax temp;
temp.real = -this->real;
temp.vir = -this->vir;
return temp;
}
//定义成员函数版完成++前置运算: 实部 +=1 虚部+=1
Complax &operator++()
{
this->real++;
this->vir++;
return *this;
}
//定义成员函数版完成++后置运算: 实部 +=1 虚部+=1
Complax operator++(int) //这里括号中的int仅仅起到占位作用,使用的是哑元
{
Complax temp;
temp.real = this->real++;
temp.vir = this->vir++;
return temp;
}
//将插入运算符重载函数,设置成友元函数
friend ostream & operator<<(ostream &L, const Complax &R);
//定义类型转换运算符重载函数
operator int()
{
return this->real + this->vir;
}
//重载小括号运算符,完成仿函数
void operator()(string msg)
{
cout << msg<< endl;
}
};
//定义全局函数版完成两个复数相减
const Complax operator-(const Complax &L, const Complax &R)
{
Complax temp; //无参构造
temp.real = L.real - R.real;
temp.vir = R.vir - R.vir;
return temp;
}
//自定义插入运算符重载函数:只能实现全局函数版
ostream & operator<<(ostream &L, const Complax &R)
{
//输出内容
if(R.vir > 0)
{
L<<R.real << "+"<<R.vir<<"i"<<endl;
}else
{
L<<R.real << R.vir<<"i"<<endl;
}
//返回左操作数自身
return L;
}
int main()
{
Complax c1(5,7); //有参构造
c1.show(); //5+7i
Complax c2(3, -2); //有参构造
c2.show(); //3-2i
cout<<"**************************************"<<endl;
Complax c3 = c1+c2; //调用加号 运算符重载函数
c3.show(); //8+5i
cout<<"**************************************"<<endl;
(c1+=c2) += c3; //调用加等于运算符重载函数
c1.show(); //16+10i
c2.show(); //3-2i
c3.show(); //8+5i
cout<<"**************************************"<<endl;
if(c1>c2) //调用关系运算符重载函数
{
cout<<"yes"<<endl;
}else
{
cout<<"no"<<endl;
}
cout<<"**************************************"<<endl;
Complax c4 = -c1; //调用负号运算符重载
c4.show(); // -16 - 10i
cout<<"**************************************"<<endl;
/*
int num = 5;
num++++; //后置自增后,返回结果是一个右值
++++num; //前置自增后,返回的结果是一个左值
*/
Complax c5 = ++++c1; //前置自增,先加再用
c5.show(); //18+12i
c1.show(); //18+12i
cout<<"**************************************"<<endl;
Complax c6 = c1++; //后置自增,先用再加
c6.show(); //18+12i
c1.show(); //19+13i
cout<<"**************************************"<<endl;
cout<<c1 <<c2; //cout.operator<<(c1);
cout<<"**************************************"<<endl;
int num = (int)c1; //调用强制类型转换函数
cout<<"num = "<<num<<endl; //32
cout<<"**************************************"<<endl;
//仿函数的调用
c1("hello world");
cout<<"**************************************"<<endl;
return 0;
}
2. 静态成员
所谓静态成员,就是将类中的某个或某几个成员,依赖于类对象而独立存
在程序编译阶段,就给静态成员分配空间,即使没有类对象,静态成员的空间依然已经分配
相当于在类体内,定义一个生命周期比较常的数据
静态成员分配,静态成员变量和静态成员函数
2.1 静态成员变量
1> 定义格式:在定义成员变量时,在前面加关键字static,该成员变量就是静态成员变量
2> 静态成员的权限一般为public,必须在全局处进行定义,类内只是声明,如果没有在全局处进行定义,那么默认为0
3> 静态成员变量,不占用类对象的内存空间,在编译时系统就分配了内存空间
4> 虽然静态成员不占用类对象的空间,但是,也可以通过类对象调用静态成员变量
5> 静态成员变量的访问:有两种访问形式
对象名.静态成员变量
类名::静态成员变量
6> 所有类对象,共享该静态成员变量,无论实例化多少个类对象,静态成员只有一份
7> 静态成员变量,从功能上来说,可以理解成是一个全局变量,但是相比于全局变量而言,静态成员变量更加体现了类的封装性
#include <iostream>
using namespace std;
class Stu
{
private:
int age;
public:
static int flag; //类内声明
public:
Stu() {}
};
//类外定义静态成员变量
int Stu::flag = 520;
int main()
{
cout<<sizeof(Stu)<<endl; //4说明静态成员 变量不占用类对象的空间
//通过类对象名直接访问静态成员变量,不用关心是否已经实例化对象了
Stu::flag = 999; //可读可写
cout<<"Stu::flag = "<<Stu::flag<<" &Stu::flag = "<<&Stu::flag<<endl; //520
//使用方法
Stu s1;
cout<<"s1.flag = "<<s1.flag<<" &s1.flag = "<<&s1.flag<<endl; //520
Stu s2;
s2.flag = 1314; //可读可写
//所有类的对象,共享静态成员变量,程序启动后,静态成员变量只有一份
cout<<"s2.flag = "<<s2.flag<<" &s2.flag = "<<&s2.flag<<endl; //1314
cout<<"s1.flag = "<<s1.flag<<" &s1.flag = "<<&s1.flag<<endl; //1314
return 0;
}
2.2 静态成员函数
1> 定义格式:在定义成员函数前加关键字static,那么该函数就是静态成员函数
2> 和静态成员变量一样,不依附于某个具体的类对象,属于整个类的
3> 静态成员函数的调用方式也有两种:通过类对象调用,通过类名直接调用
4> 静态成员函数中,只能使用静态成员变量,不能使用非静态成员变量
5> 静态成员变量中没有this指针,但是,同名的静态成员函数与非静态成员函数不构成重载关系
#include <iostream>
using namespace std;
class Stu
{
private:
int age = 18;
public:
static int flag; //类内声明
public:
Stu() {}
//非静态成员函数
void show()
{
cout<<"age = "<<this->age<<" falg = "<<this->flag<<endl;
}
//静态成员函数,与同名的非静态成员函数不构成重载关系
static void show(int)
{
cout<<" falg = "<<flag<<endl; //静态成员函数中吗,只能使用静态成员变量
}
};
//类外定义静态成员变量
int Stu::flag = 520;
int main()
{
cout<<sizeof(Stu)<<endl; //4说明静态成员 变量不占用类对象的空间、
//没有实例化对象之前,也可以直接调用静态成员函数
//Stu::show(); //非静态成员函数,不能直接调用,必须依赖于类对象而存在
Stu::show(1); //可以直接调用静态成员函数
//通过类对象名直接访问静态成员变量,不用关心是否已经实例化对象了
Stu::flag = 999; //可读可写
cout<<"Stu::flag = "<<Stu::flag<<" &Stu::flag = "<<&Stu::flag<<endl; //520
//使用方法
Stu s1;
cout<<"s1.flag = "<<s1.flag<<" &s1.flag = "<<&s1.flag<<endl; //520
Stu s2;
s2.flag = 1314; //可读可写
//所有类的对象,共享静态成员变量,程序启动后,静态成员变量只有一份
cout<<"s2.flag = "<<s2.flag<<" &s2.flag = "<<&s2.flag<<endl; //1314
cout<<"s1.flag = "<<s1.flag<<" &s1.flag = "<<&s1.flag<<endl; //1314
cout<<"**************************************************************"<<endl;
s1.show(); //非静态成员函数
s1.show(1); //使用类对象调用静态成员函数
return 0;
}
2.3 C/C++中static的总结(面试题)
1> 修饰函数的局部变量,表示将当前变量独立于整个函数而存在,其生命周期随着整个程序的编译而产生,整个程序结束而终止,即使函数没有被调用,静态成员变量的内存空间也已经存在了,即使函数以及运行结束,静态成员变量的生命周期也没有结束。一次初始化,后期调用函数时,都具有保值功能。
2> 修饰类中成员变量,为静态成员变量,不依附于类对象而存在,编译时系统分配空间,需要在全局处进行定义。即使没有类对象,也可以通过类名直接使用,也可以通过类对象进行使用。多个对象,共享静态成员变量,即使类对象的空间全部都释放后,静态成员的空间也没有结束,而是随着整个程序的结束而结束。
3> 修饰全局变量时,表示限制作用域为当前文件,只能在当前文件内使用该全局变量,其他文件不能使用,即使使用extern也不可以
4> 修饰全局函数时,表示限制作用域为当前文件,只能在当前文件内使用该函数,其他文件中不可以使用,即使使用了联合编译
5> 修饰类中的成员函数,表示静态成员函数,独立于类体而存在,也可以使用类对象进行调用。即使没有实例化对象,也可以类名调用,静态成员函数中,只能访问静态成员变量,不能访问非静态成员变量。没有this指针。
3. 继承
面向对象三大特征:封装、继承、多态
继承:所谓继承,就是基于一个已有的类,来创建出一个新类的过程叫做继承。主要提高代码的复用性。
3.1 继承的作用
1> 实现代码的复用性
2> 继承是实现多态的重要基础,没有继承就没有多态
3.2 继承的格式
class 子类名 :继承方式 class 父类名1, class 父类2。。。
{
子类扩展的成员;
};
3.3 继承方式
1> 继承方式一共有三种:public、protected、private
2> 回顾访问权限
public:能在类内、子类以及类外被访问
protected:能在类内、子类中被访问,类外不允许访问
private:只能在类内被访问,子类、类外不允许被访问
3>继承方式时父类中的所有访问权限在子类中的表现形式
父类: public|protected|private|不能访问 public|protected|private|不能访问 public|protected|private|不能访问
继承方式 public protected private
子类: public|protected|不能访问|不能访问 protected|protected|不能访问|不能访问 private|private|不能访问|不能访问
1、继承方式其实就是子类继承父类中访问权限的最高权限
2、默认继承方式是私有继承,常用的继承方式是public
4> 访问权限也可以省略不写,如果省略不写,则默认的继承方式为私有的(private)
5> 常用的继承方式是:public
#include <iostream>
using namespace std;
//定义父类
class Person
{
public:
string name = "张三"; //公共成员姓名
protected:
int pwd = 666666; //受保护成员 银行卡密码
private:
int money = 888888; //私有成员 私房钱
public:
Person() {}
Person(string n, int p, int m):name(n),pwd(p),money(m) {}
~Person() {}
void show()
{
cout<<"name = "<<name<<endl; //父类中的公共权限下的成员,父类中可以访问
cout<<"pwd = "<<pwd<<endl; //父类中的受保护权限下的成员,父类中可以访问
cout<<"money = "<<money<<endl; //父类中的私有权限下的成员,父类中可以访问
}
};
//基于一个已有的父类,定义一个新类
class Stu : public Person //公共继承
//class Stu : protected Person //受保护继承
//class Stu : Person //私有继承
{
public:
string sex = "男"; //性别
protected:
int score = 99; //受保护成员分数
private:
int age = 18; //私有成员 年龄
public:
Stu() {}
~Stu() {}
//定义show函数
void show()
{
cout<<"name = "<<name<<endl; //从父类中继承的的公共权限下的成员,子类中可以访问
cout<<"pwd = "<<pwd<<endl; //从父类中继承的受保护权限下的成员,子类中可以访问
//cout<<"money = "<<money<<endl; //从父类中继承的私有权限下的成员,子类中不可以访问
cout<<"sex = "<<sex<<endl; //子类中的公共权限下的成员,子类中可以访问
cout<<"score = "<<score<<endl; //子类中的受保护权限下的成员,子类中可以访问
cout<<"age = "<<age<<endl; //子类中的私有权限下的成员,子类中可以访问
}
};
int main()
{
cout << sizeof (Person) << endl;
cout << sizeof (Stu) << endl;
//使用子类实例化对象
Stu s1;
//s1.show();
cout<<"name = "<<s1.name<<endl; //继承的公共权限下的成员,子类中也是公共的,类外可以访问
//cout<<"pwd = "<<s1.pwd<<endl; //继承的受保护的权限下成员,子类中也是受保护的,类外无法访问
//cout<<"money = "<<s1.money<<endl; //继承的私有成员,子类中无法访问,类外也无法访问
cout<<"sex = "<<s1.sex<<endl; //子类中的公共权限下的成员类外可以访问
//cout<<"score = "<<s1.score<<endl; //子类受保护的成员,类外无法访问
//cout<<"age = "<<s1.age<<endl; //子类的私有成员,类外无法访问
return 0;
}
3.4 继承过程中的成员
1> 子类会继承父类中的所有成员,包括私有成员,只不过不能子类中不能使用父类的私有成员。如果非要使用父类的私有成员,需要在父类中提供public或者protected类型的接口函数完成对私有成员的操作。
2> 想要完成对 子类从父类中继承下来的成员的初始化工作,需要在子类的初始化列表中,显性调用父类的构造函数来完成。如果没有显性调用父类的有参构造,那么系统会自动调用父类的无参构造,来完成对继承下来的成员的初始化工作。
在这个过程中,虽然调用的父类的构造函数,但是并没有实例化父类对象,最终对象的个数只有一个
3> 类与类之间的关系模型
1、继承关系:is a 模型,是特殊的包含关系(has a模型)
2、包含关系:has a模型,在一个类中,有另一个类的成员子对象
3、友元关系:use a模型,在一个类中,使用另一类中的内容
#include <iostream>
using namespace std;
//定义父类
class Person
{
public:
string name = "张三"; //公共成员姓名
protected:
int pwd = 666666; //受保护成员 银行卡密码
private:
int money = 888888; //私有成员 私房钱
public:
Person() {cout<<"Person::无参构造"<<endl;}
Person(string n, int p, int m):name(n),pwd(p),money(m) {cout<<"Person::有参构造"<<endl;}
~Person() {cout<<"Person::析构函数"<<endl;}
void show()
{
cout<<"name = "<<name<<endl; //父类中的公共权限下的成员,父类中可以访问
cout<<"pwd = "<<pwd<<endl; //父类中的受保护权限下的成员,父类中可以访问
cout<<"money = "<<money<<endl; //父类中的私有权限下的成员,父类中可以访问
}
};
//基于一个已有的父类,定义一个新类
class Stu : public Person //公共继承
//class Stu : protected Person //受保护继承
//class Stu : Person //私有继承
{
public:
string sex = "男"; //性别
protected:
int score = 99; //受保护成员分数
private:
int age = 18; //私有成员 年龄
public:
Stu() {cout<<"Stu::无参构造"<<endl;}
Stu(string n, int p, int m,string s, int s1, int a):sex(s), score(s1), age(a) {cout<<"Stu::有参构造"<<endl;}
~Stu() {cout<<"Stu::析构函数"<<endl;}
//定义show函数
void show()
{
cout<<"name = "<<name<<endl; //从父类中继承的的公共权限下的成员,子类中可以访问
cout<<"pwd = "<<pwd<<endl; //从父类中继承的受保护权限下的成员,子类中可以访问
//cout<<"money = "<<money<<endl; //从父类中继承的私有权限下的成员,子类中不可以访问
cout<<"sex = "<<sex<<endl; //子类中的公共权限下的成员,子类中可以访问
cout<<"score = "<<score<<endl; //子类中的受保护权限下的成员,子类中可以访问
cout<<"age = "<<age<<endl; //子类中的私有权限下的成员,子类中可以访问
}
};
int main()
{
cout << sizeof (Person) << endl;
cout << sizeof (Stu) << endl;
//使用子类实例化对象
Stu s1("zhangpp", 111111, 100, "男", 99, 18); //有参构造
//s1.show();
cout<<"name = "<<s1.name<<endl; //继承的公共权限下的成员,子类中也是公共的,类外可以访问
//cout<<"pwd = "<<s1.pwd<<endl; //继承的受保护的权限下成员,子类中也是受保护的,类外无法访问
//cout<<"money = "<<s1.money<<endl; //继承的私有成员,子类中无法访问,类外也无法访问
cout<<"sex = "<<s1.sex<<endl; //子类中的公共权限下的成员类外可以访问
//cout<<"score = "<<s1.score<<endl; //子类受保护的成员,类外无法访问
//cout<<"age = "<<s1.age<<endl; //子类的私有成员,类外无法访问
cout<<"**********************************************"<<endl;
Person p1;
cout<<"&p1 = "<<&p1<<" &p1.name = "<<&p1.name<<endl;
cout<<"**********************************************"<<endl;
cout<<"&s1 = "<<&s1<<" &s1.name = "<<&s1.sex<<endl;
cout<<"**********************************************"<<endl;
return 0;
}