目录
一、类
1.C++中的类
是由结构体演化而来的。
2.声明一个类
class 类名{
private: //后面是冒号
//私有的成员变量(数据/属性)和成员函数(方法/行为)
protected:
//受保护的成员变量(数据/属性)和成员函数(方法/行为)
public:
//共有的成员变量(数据/属性)和成员函数(方法/行为)
};//别忘了写分号
例:
#include <iostream>
using namespace std;
//圆类
class Circel{
public:
double r;//半径
double l;//周长
double get_l(){
l = 2 * 3.14 * r;
return l;
}
};
int main()
{
//使用类定义变量的过程我们称之为:实例化类对象
class Circel c1;//此处的class可以不写
c1.r = 2;
cout<<c1.get_l()<<endl;//12.56
Circel c2; //c1 和 c2 都叫做Circel类的对象 是相互独立的
c2.r = 3;
cout<<c2.get_l()<<endl;//18.84
return 0;
}
3.封装
面向对象的三大特征(封装、继承、多态),如果有四,加一个抽象
我们对类的定义,就属于封装的范畴。
封装:将和一类事物相关的完事万物都封装成类,通过类对象去做事儿。
访问控制权限:
public: 修饰的成员 在类内、子类中、类外都可以直接访问
protected: 修饰的成员 在类内和子类中可以访问 类外不能直接访问
private: 修饰的成员 只能在类内访问 子类中和类外都不能直接访问
类的成员函数可以访问类的成员变量(public,protected,private都可以访问)
即使是通过函数传参传递过来的本类的对象的私有成员,也能访问。
也就是说,访问控制针对的是整个类,而不是某个对象而言的。
访问控制一旦出现,后面所有的成员都受该权限的控制
直到出现新的访问控制权限,或者类的定义结束
访问控制权限允许多从出现和使用,但是一般情况下,
我能都通过对成员的归类,一般一种权限只写一次。
如果类中没有写访问控制权限,默认的所有成员都是被private控制的。
之所以有访问控制权限,是因为,类的设计者认为:
类的使用者不一定按照正常的方式去使用他,如上面的圆类中,半径可能被赋成负值,不合理
#include <iostream>
using namespace std;
//圆类
class Circel{
private:
//一般情况下 类的成员变量都是私有的,不允许在类外直接访问
double r;//半径
double l;//周长
public:
//一般会提供public权限的成员函数给使用者 方便对成员变量的操作
int set_r(int value){
//类的设计者可以在对外体中的成员函数中
//对参数做检查,防止使用者乱用
if(value < 0){
cout<<"半径不合理,设置失败"<<endl;
return -1;
}
r = value;//成员函数可以访问成员变量
return 0;
}
double get_l(){
l = 2 * 3.14 * r;
return l;
}
};
int main()
{
//Circel c1;
//c1.r = -2;//由于 成员r权限是 private
//在类外无法直接访问
//cout<<c1.get_l()<<endl;//12.56
Circel c2;
c2.set_r(2);//通过成员函数访问private的成员变量 是允许的
cout<<c2.get_l()<<endl;
Circel c3;
c3.set_r(-10);//不按照规则使用 是无法操作类的成员变量的
return 0;
}
练习1:
设计一个长方体的类
成员变量:长、宽、高
成员函数:获取体积
实例化一个对象 调用并测试
练习2:
在上一题的基础上,添加一个全局函数,功能:判断两个长方体是否相等。
条件:长==长 宽==宽 高==高
#include <iostream>
#include "03.h"
using namespace std;
/*class tangle{
private:
double lenth;
double width;
double height;
public:
void init(double l,double w,double h){
if(l<0 || w<0 || h<0){
cout<<"输入参数不合理,请重新输入"<<endl;
exit(-1);
}
lenth = l;
width = w;
height = h;
}
double getvol(double l,double w,double h){
double v=lenth*width*height;
return v;
}
double getchang(){
return lenth;
}
double getkuan(){
return width;
}
double getgao(){
return height;
}
bool Isequalclass(tangle &t2){
if(lenth==t2.lenth && width==t2.width && height==t2.height){
return 1;
}else{
return 0;
}
}
};*/
bool Isequal(tangle &t1,tangle &t2){
if((t1.getchang()==t2.getchang()) && (t1.getkuan()==t2.getkuan()) && (t1.getgao()==t2.getgao()) ){
return true;
}else {
return false;}
}
int main()
{
/*double l=10;
double w=2;
double h=-3;
tangle t;
cout<<t.getvol(l,w,h)<<endl;*/
tangle t1;
t1.init(10,20,30);
tangle t2;
t2.init(10,10,30);
/*全局变量版
if(Isequal(t1,t2)){
cout<<"相等"<<endl;
}else {
cout<<"不相等"<<endl;
}*/
if(t1.Isequalclass(t2)){
cout<<"相等"<<endl;
}else {
cout<<"不相等"<<endl;
}
return 0;
}
多文件编程
.h
#ifndef _03_H_
#define _03_H_
#include <iostream>
using namespace std;
class tangle{
private:
double lenth;
double width;
double height;
public:
void init(double l,double w,double h);
double getvol(double l,double w,double h);
double getchang();
double getkuan();
double getgao();
bool Isequalclass(tangle &t2);
};
#endif/*_03_H_*/
.cpp
#include <iostream>
#include "03.h"
using namespace std;
bool Isequal(tangle &t1,tangle &t2){
if((t1.getchang()==t2.getchang()) && (t1.getkuan()==t2.getkuan()) && (t1.getgao()==t2.getgao()) ){
return true;
}else {
return false;}
}
int main()
{
/*double l=10;
double w=2;
double h=-3;
tangle t;
cout<<t.getvol(l,w,h)<<endl;*/
tangle t1;
t1.init(10,20,30);
tangle t2;
t2.init(10,10,30);
/*全局变量版
if(Isequal(t1,t2)){
cout<<"相等"<<endl;
}else {
cout<<"不相等"<<endl;
}*/
if(t1.Isequalclass(t2)){
cout<<"相等"<<endl;
}else {
cout<<"不相等"<<endl;
}
return 0;
}
二、C++中类和结构体的区别
唯一的区别,就是默认的访问控制权限不同:
类中成员的默认的访问权限:private
结构体中成员的默认的访问权限:public
#include <iostream>
using namespace std;
class A{
int m;
int n;
void show(){
cout<<m<<" "<<n<<endl;
}
};
struct B{
int m;
int n;
void show(){
cout<<m<<" "<<n<<endl;
}
};
int main()
{
A hqyj1;
#if 0
//class的默认访问权限是 private 在类外无法访问成员
hqyj1.m = 10;
hqyj1.n = 20;
hqyj1.show();
#endif
//struct的默认访问权限是 public 在类外可以访问成员
B hqyj2;
hqyj2.m = 10;
hqyj2.n = 20;
hqyj2.show();
return 0;
}
既然C++中结构体和类区别不大,什么时候使用类,什么时候使用结构体呢?
一般情况下,定义数据节点的时候,都使用结构体 如链表的节点
而封装的逻辑比较多的时候,一般都是用类
如果分不清,就都是用class
三、this指针
this指针是成员函数中隐藏的一个形参,
哪个对象调,就指向谁
复习:const修饰指针
const int *p;//不能通过p修改指向的空间的内容
int const *p;//不能通过p修改指向的空间的内容
int * const p;//不能修改p的指向
const int * const p;//都不能修改
this指针类型:类名 * const this
注:
1.不能在成员函数形参中使用this指针
2.不能再构造函数的初始化表中使用this指针
3.在成员函数的函数体中可以使用this指针
例:
#include <iostream>
using namespace std;
class Test{
private:
int a;
int b;
public:
void init(int x, int y){
//在成员函数内部 不加任何修饰直接访问成员变量
//都是通过this指针访问的
//a = x;
//b = y;
this->a = x;
this->b = y;
}
bool compare(Test &x){
if(this->a == x.a && this->b == x.b){
//下面的用法也可以
//在成员函数内部 不加任何修饰直接访问成员变量
//都是通过this指针访问的
//if(a == x.a && b == x.b){
return true;
}
return false;
}
};
int main()
{
Test t1;
t1.init(10, 20);
Test t2;
t2.init(30, 40);
cout<<(t1.compare(t2) ? "相等" : "不相等")<<endl;
return 0;
}
必须使用this指针的情况
成员函数形参名和成员变量相同时(也可以使用 构造函数 的初始化表解决)
#include <iostream>
using namespace std;
class Test{
private:
int a;
int b;
public:
void init(int a, int b){
//成员函数的形参和成员变量重名时
//在成员函数内部 不加修饰 默认使用的是形参
//a = a;//形参自己给自己赋值
//b = b;//形参自己给自己赋值
//这是可以使用this指针区分
this->a = a;
this->b = b;
cout<<"init: "<<a<<" "<<b<<endl;//10 20
}
void show(){
cout<<"show: "<<a<<" "<<b<<endl;//10 20
}
};
int main()
{
Test t1;
t1.init(10, 20);
t1.show();
return 0;
}
另一种必须使用this指针的场景:
拷贝赋值函数需要返回自身的引用时,只能通过this指针访问。
四、类中特殊的成员函数
1.构造函数
1.1 功能
实例化对象的时候给成员变量申请空间,完成成员变量初始化
还可以执行一些分配内存、打开文件等操作
1.2 格式
函数名与类名相同,
不关注返回值,
构造函数一般受public权限控制
1.3 调用时机
在类实例化对象的过程中,会自动调用构造函数。
构造函数不能手动调用
栈区:
类名 变量名(构造函数的实参表);//调用构造函数
堆区:
类名 *指针名//这个过程不会调用构造函数
指针名 =new 类名构造函数的实参表); //这个过程才会调用构造函数
#include <iostream>
#include <string>
using namespace std;
class student{
string name;
int *age;
//构造函数
public:
student(string n,int a){
cout << "我是构造函数" << n <<endl;
name=n;
age=new int(a);
}
void show(){
cout<<name<<" "<<*age<<endl;
}
};
int main()
{
//栈区实例化对象
Student s1("zhangsan", 10);//调用构造函数
s1.show();
//堆区实例化对象
Student *p1;
p1 = new Student("lisi", 20);//调用构造函数
p1->show();
//malloc不会调用构造函数
Student *p2;
p2 = (Student *)malloc(sizeof(Student));
return 0;
}
练习:
把前一天长方体的类中init函数,该成构造函数,调用并测试。
#include <iostream>
using namespace std;
class tangle{
private:
double lenth;
double width;
double *height;
public:
/*tangle(double l,double w,double h){
if(l<0 || w<0 || h<0){
cout<<"输入参数不合理,请重新输入"<<endl;
exit(-1);
}
cout << "我是构造函数" <<endl;
lenth = l;
width = w;
height = h;
}*/
tangle(double l,double w,double h);//类内声明
void show(){
cout<<lenth<<" "<<width<<" "<<height<<endl;
}
//析构函数
~tangle(){
if(height !=NULL){
delete height;
}
}
};
//类外定义
tangle::tangle(double l,double w,double h){
if(l<0 || w<0 || h<0){
cout<<"输入参数不合理,请重新输入"<<endl;
exit(-1);
}
cout << "我是构造函数" <<endl;
lenth = l;
width = w;
height = new double(h);
}
int main()
{
tangle t1(10,20,30);
tangle *t2;
t2=new tangle(10,20,20);
return 0;
}
1.4 构造函数支持重载
默认构造函数:
如果类型没有显性的定义构造函数
编译器会提供一个默认的构造函数,
形参列表为void,函数体为空,用来给实例化对象的过程使用。
如果类中显性的定义了构造函数,那么编译器就不再提供默认的版本了
所以,在这种场景下,如果想要使用无参的构造函数,也需要显性手动定义
#include <iostream>
#include <string>
using namespace std;
class student{
private:
string name;
int age;
public:
//无参
student(){cout<<"我是无参构造函数"<<endl;}
//有参
student(string n,int a){
cout<<"我是you参构造函数1"<<endl;
name=n;
age=a;
}
student(string n){
cout<<"我是you参构造函数2"<<endl;
name=n;
}
void show(){
cout<<name<<" "<<age<<endl;
}
};
int main()
{
student s1("zhangsan", 10);//调用有参构造函数1
s1.show();
student s2;//无参构造函数
s2.show();
student s3("hello");//调用有参构造函数2
s3.show();
return 0;
}
1.5 构造函数的初始化列表
可以在定义构造函数的时候,使用 冒号的方式 引出构造函数的初始化列表
格式:
类名(构造函数的形参表):成员1(初值1),成员2(初值2){
构造函数的函数体;
}
#include <iostream>
#include <string>
using namespace std;
class student{
private:
string name;
int age;
public:
//无参
student(){cout<<"我是无参构造函数"<<endl;}
//有参
student(string n,int a):name(n),age(a){
cout<<"我是you参构造函数1"<<endl;
}
student(string n):name(n){
cout<<"我是you参构造函数2"<<endl;
}
void show(){
cout<<name<<" "<<age<<endl;
}
};
int main()
{
student s1("zhangsan", 10);//调用有参构造函数1
s1.show();
student s2;//无参构造函数
s2.show();
student s3("hello");//调用有参构造函数2
s3.show();
return 0;
}
1.6 必须使用初始化列表的场景
场景1.成员函数形参名和成员变量名相同(也可以使用this指针解决)
#include <iostream>
using namespace std;
#include <string>
#include <cstdlib>
class Student{
private:
string name;
int *age;
public:
Student():name(""),age(new int){
cout<<"我是无参构造函数"<<endl;
}
//重名了 可以使用初始化表来区分
Student(string name, int age):name(name), age(new int(age)){
cout << "我是有参构造函数" <<endl;
}
void show(){
cout<<name<<" "<<*age<<endl;
}
};
int main()
{
//栈区实例化对象
Student s1("zhangsan", 10);//调用有参构造函数1
s1.show();
Student s2;//无参构造函数
s2.show();
return 0;
}
场景2.
类中有引用作为成员变量的时候
#include <iostream>
using namespace std;
#include <iostream>
#include <string>
using namespace std;
class student{
private:
string name;
int &age;
public:
//有参
student(string n,int &a):name(n),age(a){
//int &a=num;
//int &age=a;
cout<<"我是you参构造函数1"<<endl;
}
void show(){
cout<<name<<" "<<age<<endl;
}
};
int main()
{
//student s1("zhangsan", 10);//调用有参构造函数1
int num=10;
student s1("zhangsan", num);//调用有参构造函数1
s1.show();
return 0;
}
场景3.
类中有const修饰的变量
#include <iostream>
using namespace std;
#include <iostream>
#include <string>
using namespace std;
class student{
private:
string name;
const int age;
public:
//有参
student(string n,int a):name(n),age(a){
//如果不采用成员初始化列表的话,后续也无法给age赋值了,没有意义
cout<<"我是you参构造函数1"<<endl;
}
void show(){
cout<<name<<" "<<age<<endl;
}
};
int main()
{
//student s1("zhangsan", 10);//调用有参构造函数1
int num=10;
student s1("zhangsan", num);//调用有参构造函数1
s1.show();
return 0;
}
场景4.
当类中有成员子对象(其他类的对象)时
#include <iostream>
using namespace std;
#include <iostream>
#include <string>
using namespace std;
class student{
private:
string name;
int age;
public:
student(){cout << "Student 无参构造函数" <<endl;}
//有参
student(string n,int a):name(n),age(a){
cout<<"我是you参构造函数1"<<endl;
}
void show(){
cout<<name<<" "<<age<<endl;
}
};
class teacher{
private:
string name;
int age;
student stu;
public:
teacher(){cout << "Student 无参构造函数" <<endl;}
teacher(string n,int a,string sn,int sa):name(n),age(a),stu(sn,sa){
cout<<"我是you参构造函数1"<<endl;
//name=n;
//age=a;
//stu.Student(n2, a2);//错误的 构造函数不能手动调用
}
void show(){
cout<<name<<" "<<age<<" "<<endl;
//cout<<stu.name<<" "<<stu.age<<endl;//错误的 name 和 age 是 Student类中的私有的
stu.show();
}
};
int main()
{
//栈区实例化对象
//调用Teacher的有参构造函数 调用Student的有参构造
teacher c1("zhangsan", 20, "xiaoming", 18);
c1.show();
//调用Teacher的无参构造函数 调用Student的无参构造
teacher c2;
return 0;
2.析构函数
2.1 作用
在对象消亡的时候,用来做释放空间等善后工作的。
2.2 格式
~类名(void){} //析构函数是没有参数的 所以不能重载
2.3 调用时机
对象消亡时,自动调用。 (析构函数可以手动调用 但是一般不这样做)
栈区:声明周期结束时
堆区:手动调用delete时
2.4 默认析构函数
如果类中没有显性的定义析构函数,编译器会默认提供一个函数体为空的析构函数,
用来消亡对象使用的,如果显性定义了,默认的版本就不提供了。
例:
#include <iostream>
using namespace std;
#include <string>
class Student{
private:
string name;
int *age;
public:
Student():name(""), age(NULL){cout<<"无参构造"<<endl;};
Student(string n, int a):name(n), age(new int(a)){
cout<<"有参构造"<<endl;
};
#if 0
//类内定义的写法
~Student(void){
cout<<"析构函数"<<endl;
if(age != NULL){
delete age;
age = NULL;
}
}
#endif
//类内声明
~Student(void);
};
//类外定义的写法
Student::~Student(void){
cout<<"析构函数"<<endl;
if(age != NULL){
delete age;
age = NULL;
}
}
int main()
{
Student s1;//无参构造
Student s2("zhangsan", 20);//有参构造
Student *p1 = new Student;//无参构造
Student *p2 = new Student("lisi", 18);//有参构造
//堆区的对象 需要delete时 自动调用析构函数 完成善后工作
delete p1;//如果不手动调用delete 析构函数不会被调用
delete p2;
//栈区的对象 在声明周期结束时,自动调用析构函数 完成善后工作
return 0;
}
构造函数和析构函数调用的顺序?
1.对于堆区的对象:
先new哪个对象,就先构造哪个对象,
先delete哪个对象,就先析构哪个对象
所以,一般不考虑堆区的构造函数和析构函数的调用顺序
2.对于栈区的对象:
构造函数的调用顺序:按顺序调用
析构函数的调用顺序:逆序调用
----先构造的后析构,栈的顺序
3.拷贝构造函数
3.1 格式
函数名:与类同名
返回值:没有返回值
形参: const 类名 &
类名 (const 类名 &other){}
3.2 调用时机
用一个已经初始化的对象初始化一个新的对象时,会自动调用拷贝构造函数
类名 对象1(构造函数的实参表); // 有参构造函数
类名 对象2(对象1); //拷贝构造函数
类名 对象3 = 对象1; //拷贝构造函数
类名 *指针名 = new 类名(对象3);//拷贝构造函数
//类名 对象2(对象1);----从编译器的角度理解 相当于 对象2.类名(对象1);
#include <iostream>
using namespace std;
#include <string>
class Student{
private:
string name;
int age;
public:
Student(){cout<<"无参构造"<<endl;}
Student(string n, int a):name(n), age(a){
cout<<"有参构造"<<endl;
};
#if 0
Student(const Student &other){
cout<<"拷贝构造"<<endl;
name = other.name;
age = other.age;
}
#endif
//拷贝构造函数 也是构造函数,也可以使用初始化表
Student(const Student &other):name(other.name), age(other.age){
cout<<"拷贝构造"<<endl;
}
~Student(void){cout<<"析构函数"<<endl;}
void show(){
cout<<name<<" "<<age<<endl;
}
};
int main()
{
Student s1("zhangsan", 20);//有参构造函数
s1.show();
Student s2(s1);//拷贝构造
s2.show();
Student s3 = s1;//拷贝构造
s3.show();
Student *p1 = new Student(s1);//拷贝构造
p1->show();
delete p1;
Student s4;//无参构造
s4 = s1;//调用拷贝赋值函数 不会调用拷贝构造
return 0;
}
笔试面试题:
C++中浅拷贝和深拷贝的区别?
浅拷贝:
如果不手动提供一个拷贝构造函数,则编译器默认会给一个拷贝构造函数,编译器提供的拷贝构造函数只会进行简单的赋值。如果有指针成员变量也只会简单的赋值,将指针变量里的值赋值给新的对象,这样两个指针变量指向的就是同一块空间,那么在析构的时候会出现double free的问题,此时就需要深拷贝来解决问题。
深拷贝:
由程序员手动的写一个拷贝构造函数,给对象里的指针变量重新开辟一块空间,将原来对象指针里的内容拷贝给新对象的指针,这样析构的时候,释放的就是各自的空间,可以有效避免double free的问题。
4.拷贝赋值函数
4.1 格式
类名 &operator=(const 类名 &other){
if(this!=&other){
retutn *this;
}
4.2 调用时机
两个已经完成初始化的类对象之间相互赋值的时候,会自动调用拷贝赋值函数。
例:
string s1("hello"); //有参构造
string s2(s1); //拷贝构造
string s3; //无参构造
s3 = s1; //调用拷贝赋值函数
----从编译器的角度 相当于 s3.operator=(s1);
例:
#include <iostream>
using namespace std;
#include <string>
class Student{
private:
string name;
int age;
public:
Student(){cout<<"无参构造"<<endl;}
Student(string n, int a):name(n), age(a){
cout<<"有参构造"<<endl;
};
Student(const Student &other):name(other.name), age(other.age){
cout<<"拷贝构造"<<endl;
}
~Student(void){cout<<"析构函数"<<endl;}
#if 0
//类内定义的写法
Student &operator=(const Student &other){
cout<<"拷贝赋值"<<endl;
if(this != &other){
name = other.name;
age = other.age;
}
return *this;//返回自身的引用
}
#endif
//类内声明
Student &operator=(const Student &other);
void show(){
cout<<name<<" "<<age<<endl;
}
};
//类外定义的写法
Student &Student::operator=(const Student &other){
cout<<"拷贝赋值"<<endl;
if(this != &other){
name = other.name;
age = other.age;
}
return *this;//返回自身的引用
}
int main()
{
Student s1("zhangsan", 20);//有参构造函数
s1.show();
Student s2;//无参构造
s2 = s1;//拷贝赋值
s2.show();
Student s3;
s3 = s2 = s1;//返回的自身的引用是为了级联使用时用到的 从右到左结合
//从编译器的角度 s3.operator=(s2.operator=(s1))
s3.show();
return 0;
}
如果类中没有显性的给定拷贝赋值函数,编译器会提供一个默认的拷贝赋值函数
完成两个对象的成员之间的简单赋值(浅拷贝),如果类中有指针成员,就得自己显性
定义深拷贝的写法,写法如下面的例子:
#include <iostream>
using namespace std;
#include <string>
class Student{
private:
string name;
int *age;
public:
Student():name(""), age(new int){cout<<"无参构造"<<endl;}
Student(string n, int a):name(n), age(new int (a)){
cout<<"有参构造"<<endl;
};
Student(const Student &other):name(other.name), age(new int (*(other.age))){
cout<<"拷贝构造"<<endl;
}
~Student(void){
cout<<"析构函数"<<endl;
if(NULL != age){
delete age;
age = NULL;
}
}
//深拷贝
Student &operator=(const Student &other){
if(this != &other){
this->name = other.name;
//先释放指针原来指向的空间
delete this->age;
//再根据实际需求重新分配对应大小的新的空间
age = new int(*(other.age));
//我的这个例子中 不重新分配也可以 因为两个对象的age都是int *
//而如果char *指针 用来保存字符串 那么就需要根据字符串的长度重新分配了
}
return *this;
}
void show(){
cout<<name<<" "<<*age<<endl;
}
};
int main()
{
Student s1("zhangsan", 20);//有参构造函数
s1.show();
Student s2("lisi", 18);//有参构造函数
s2 = s1;//拷贝赋值
s2.show();
return 0;
}
笔试面试题:
C++中一个空类Test,默认会提供哪些函数,请指明并写出声明的格式。
Test(){}
~Test(){}
Test (const Test &other){}
Test &operator=(){}
八、运算符重载
1.概念
所谓的运算符重载,就是给运算符赋予一个新的含义,让本来只能做基本类型运算的运算符,
也能做类对象之间的运算,如果没有运算符重载,类对象之前是不能直接做运算的。
2 .重载的方法
运算符重载本质也是函数重载,我们需要重新定义函数 operator# (#表示运算符)
3.操作数
个数:
由要重载的运算符本身决定: + 需要两个操作需 ++就只需要一个操作数
类型:
是由我们自己定义的
4 运算符重载函数的调用
左调右参,一般是由左操作数来调用运算符重载函数,右操作数作为参数。
如:s1 = s2 ---> 从编译器的角度 s1.operator=(s2)
5 注意事项
1.运算符重载的格式是约束好的,但是逻辑是我们自己规定的,但是我们实现时,
一般都要尽量保留运算符的含义,不要乱写,如+重载中 写减法的逻辑
2.运算符重载可以实现成员函数版,也可以实现全局函数版,
但是为了访问私有成员方便,我们一般都写成成员函数版
注意:全局函数版和成员函数版只能实现一个,否则调用时,有歧义,会报错
6 运算符重载的格式
6.1算术运算符重载
+ - * / %
表达式:L # R (L 左操作数 # 运算符 R 右操作数)
左操作数:既可以是左值,又可以是右值 a+b 1+2(左右操作数都可以既是常量,又是变量)
右操作数:既可以是左值,又可以是右值
表达式的结果:只能是右值 c=L+R (L+R=c不行)
成员函数版:
从编译器的角度:L.operator#(R)
const 类名 operator#(const 类名 &R)const//不引用,返回的是L+R的和
全局函数版:
从编译器的角度:operator#(L,R)
friend const 类名 operator#(const 类名 &L,const 类名 &R)--友元是为了访问私有成员方便
//类内
friend Complex const operator+(const Complex &L,const Complex &R);//类名前加const:返回值是一个右值,不能被改变
const Complex operator-(const Complex &R)const{//const修饰this指针,确保L的值不能被修改
Complex temp;
temp.real=real-R.real;
temp.vir=vir-R.vir;
return temp;
}
//类外
const Complex operator+(const Complex &L,const Complex &R){
Complex temp;
temp.real=L.real+R.real;
temp.vir=L.vir+R.vir;
return temp;
}
6.2关系运算符重载
> < >= == !=
表达式:L # R (L 左操作数 # 运算符 R 右操作数)
左操作数:既可以是左值,又可以是右值 a>b 1<2(左右操作数都可以既是常量,又是变量)
右操作数:既可以是左值,又可以是右值
表达式的结果:只能是右值 比较的结果是布尔类型,是常量
成员函数版:
从编译器的角度:L.operator#(R)
const bool 类名 operator>=(const 类名 &R)const;
全局函数版:
从编译器的角度:operator#(L,R)
friend const bool 类名 operator>=(const 类名 &L,const 类名 &R);
//类内
friend const bool operator<(const Complex&L,const Complex &R);
const bool operator>(const Complex &R)const{
if(real>R.real &&vir>R.vir){
return true;
}
return false;
}
//类外
const bool operator<(const Complex&L,const Complex &R){
if(L.real<R.real &&L.vir<R.vir){
return true;
}
return false;
}
6.3赋值运算符重载
= += -= *= /= ...
表达式:L # R (L 左操作数 # 运算符 R 右操作数)
左操作数:只能是左值
右操作数:既可以是左值,又可以是右值
表达式的结果:左操作数
成员函数版:
从编译器的角度:L.operator#(R)
类名 &operator+=(const 类名 &R);
全局函数版:
从编译器的角度:operator#(L,R)
friend 类名 &operator+=(类名 &L,const 类名 &R);
注意:赋值类运算符中的 = ,只能实现成员函数版
因为编译器默认提供的特殊的成员函数 拷贝赋值函数 本质就是=运算符的重载
//类内
friend Complex& operator-=(Complex&L, const Complex &R);
Complex &operator+=(const Complex &R){
real+=R.real;
this->vir+=R.vir;
return *this;
}
//类外
Complex& operator-=(Complex &L, const Complex &R){
L.real-=R.real;
L.vir-=R.vir;
return L;
}
6.4单目运算符重载
-(负) !(非) ~(取反)
表达式:#O (# 运算符 O操作数)
操作数:左值
表达式的结果:右值
成员函数版:
从编译器的角度:O.operator#()
const 类名 operator-()const;
全局函数版:
从编译器的角度:operator#(&O)
friend const 类名 &operator+=(const 类名 &O);
//类内
friend const Complex operator-(const Complex &O);
const Complex operator-()const{
Complex temp;
temp.real=-real;
temp.vir=-(this->vir);
return temp;
}
//类外
const Complex operator-(const Complex &O){
Complex temp;
temp.real=-O.real;
temp.vir=-(O.vir);
return temp;
}
6.5自增自减运算符重载
6.5.1 前缀的自增自减运算符
++a --a(常量不能自增自减)
表达式:# O (# 运算符 O操作数)
操作数:左值
表达式的结果:左值
成员函数版:
从编译器的角度:O.operator++()
类名 &operator++();
全局函数版:
从编译器的角度:
friend 类名 &operator++(类名 &O);
//类内
friend Complex &operator--(Complex &O);
//++
Complex &operator++(){
this->real++;
this->vir++;
return *this;
}
//类外
Complex &operator--(Complex &O){
O.real--;
O.vir--;
return O;
}
6.5.2 后缀的自增自减运算符
a++ a--(常量不能自增自减)
表达式:# O (# 运算符 O操作数)
操作数:左值
表达式的结果:右值
成员函数版:
从编译器的角度:O.operator++()
const 类名 operator++(int ); //后置++与前置++仅返回值不同,无法构成重载,需要哑元
全局函数版:
从编译器的角度:
friend const 类名 operator++(类名 &O,int);
//类内
friend const Complex operator--(Complex &O,int);
//后++
const Complex operator++(int){
Complex temp=*this;
this->real++;
this->vir++;
return temp;
}
//类外
//后--
const Complex operator--(Complex &O,int){
Complex temp=O;
O.real--;
O.vir--;
return temp;
}
完整代码
#include <iostream>
#include <string>
using namespace std;
class Complex{
private:
int real;
int vir;
public:
Complex(){}
Complex(int r,int v):real(r),vir(v){}
~Complex(){}
void show()const{
cout<<real<<"+"<<vir<<"i"<<endl;
}
friend Complex const operator+(const Complex &L,const Complex &R);//类名前加const:返回值是一个右值,不能被改变
friend const bool operator<(const Complex&L,const Complex &R);
friend const Complex operator-(const Complex &O);
friend Complex& operator-=(Complex&L, const Complex &R);
friend Complex &operator--(Complex &O);
friend const Complex operator--(Complex &O,int);
//-
const Complex operator-(const Complex &R)const{//const修饰this指针,确保L的值不能被修改
Complex temp;
temp.real=real-R.real;
temp.vir=vir-R.vir;
return temp;
}
//>
const bool operator>(const Complex &R)const{
if(real>R.real &&vir>R.vir){
return true;
}
return false;
}
//+=
Complex &operator+=(const Complex &R){
real+=R.real;
this->vir+=R.vir;
return *this;
}
//取反
/*const Complex operator-()const{
Complex temp;
temp.real=-real;
temp.vir=-(this->vir);
return temp;
}*/
//前++
Complex &operator++(){
this->real++;
this->vir++;
return *this;
}
//后++
const Complex operator++(int){
Complex temp=*this;
this->real++;
this->vir++;
return temp;
}
};
//全局版
//前--
Complex &operator--(Complex &O){
O.real--;
O.vir--;
return O;
}
//后--
const Complex operator--(Complex &O,int){
Complex temp=O;
O.real--;
O.vir--;
return temp;
}
//取反
const Complex operator-(const Complex &O){
Complex temp;
temp.real=-O.real;
temp.vir=-(O.vir);
return temp;
}
//-=
Complex& operator-=(Complex &L, const Complex &R){
L.real-=R.real;
L.vir-=R.vir;
return L;
}
//+
const Complex operator+(const Complex &L,const Complex &R){
Complex temp;
temp.real=L.real+R.real;
temp.vir=L.vir+R.vir;
return temp;
}
//<
const bool operator<(const Complex&L,const Complex &R){
if(L.real<R.real &&L.vir<R.vir){
return true;
}
return false;
}
int main()
{
Complex c1(10,20);
Complex c2(1,2);
++c1;
c1.show();
--c1;
c1.show();
(c1++).show();
c1.show();
(c2--).show();
c2.show();
return 0;
}
6.6 插入>>和提取<<
cin 和 cout 分别是是 istream 和 ostream 类的对象
namespace std{
istream cin;
ostream cout;
}
int a = 10;
cout<<a;// 从编译器的角度 cout.operator
对于插入>>和提取<<运算符的重载,只能实现全局函数版,
因为我们无法修改 istream 和 ostream 类
提取<<运算符重载的格式
friend ostream &operator<<(ostream &O,类名 &x);
提取<<运算符重载的格式
friend ostream &operator>>(istream &O,类名 &x);
6.7 不能重载的运算符
. 取成员运算符
:: 作用域限定符
.* 成员指针运算符
?: 三目运算符
# 预处理符
sizeof运算符
九、静态成员
1.静态成员变量
在定义成员变量的时候,前面加关键字 static 修饰,这个成员变量就是一个静态成员变量。
1.静态成员变量必须初始化,且必须在类外全局处初始化
2.所有的类对象共享同一个静态成员变量,一个类对象将其修改,
其他对象访问时,访问的也是修改之后的值
也就是说,静态成员变量是属于整个类的,而不是属于某个类对象的
#include <iostream>
using namespace std;
#include <string>
class Student{
private:
string name;
int age;
public:
//静态成员变量也受访问控制权限的控制
static int flag;//下课的标志位 10 没下课 20 下课
Student(){}
//构造函数的初始阿虎表中不能初始化静态成员变量
//Student(string n, int a):name(n), age(a), flag(100){
// flag = 100;//构造函数的函数体中可以访问 但是已经不是初始化了 是赋值
//}
Student(string n, int a):name(n), age(a){}
~Student(){}
};
//静态成员变量必须初始化 且必须在类外全局处初始化
int Student::flag = 20;
int main()
{
Student s1("张三", 18);
cout<<(s1.flag==10?"下课":"没下课")<<endl;
//访问静态成员变量的方式1:通过类对象访问
Student s2("李四", 20);
cout<<(s2.flag==10?"下课":"没下课")<<endl;
//访问静态成员变量的方式2:通过类名直接访问
cout<<Student::flag<<endl;
//一个类对象改变了静态成员变量 其他类对象访问时 也是修改后的值
s1.flag = 10;
cout<<(s2.flag==10?"下课":"没下课")<<endl;
return 0;
}
2.静态成员函数
在定义成员函数的时候,前面加关键字 static 修饰,这个成员函数就是一个静态成员函数。
1.静态成员变量是属于整个类的,而不是属于某个类对象的
2.静态成员函数没有this指针,不能访问普通的成员变量,只能访问静态成员变量
#include <iostream>
using namespace std;
#include <string>
class Student{
private:
string name;
int age;
public:
static int flag;//下课的标志位 10 没下课 20 下课
Student(){}
Student(string n, int a):name(n), age(a){}
~Student(){}
//静态成员函数 没有this指针 只能访问静态成员变量
static void show(){
//cout<<name<<endl;//错误的
//cout<<age<<endl;//错误的
cout<<flag<<endl;
}
};
//静态成员变量必须初始化 且必须在类外全局处初始化
int Student::flag = 20;
int main()
{
//静态成员函数的访问方式1:通过类对象访问
Student s1("张三", 18);
s1.show();
//静态成员函数的访问方式2:通过类名直接访问
Student::show();
return 0;
}
为什么要使用静态成员变量?为了让成员变量的存在不依赖于类对象。
为什么要使用静态成员函数?为了让函数逻辑的执行不依赖于类对象。