C++回顾 Day6

发布于:2025-05-28 ⋅ 阅读:(94) ⋅ 点赞:(0)

运算符重载总结

三目运算符不能进行重载

双目运算符:L#M

双目运算符重载为全局的友元函数:operator#(L,M)

双目运算符重载为成员函数:L.operator#(M)

单目运算符:#M M#

单目运算符重载为全局的友元函数:operator#(M)

单目运算符重载为成员函数:M.operator()

对于负号-的重载

#include <iostream>
using namespace std;

class complex
{
	public:
		complex(int x = 0,int y = 0):x(x),y(y){}
		const complex operator-() const
		{
			return complex(-this->x,-this->y);
		}
		void myshow() const
		{
			cout << '(' << x << ',' << y << ')' << endl;
		}
	private:
		int x;
		int y;
};

int main()
{
	int a = 5;
	cout << -a << endl;
	cout << -(-a) << endl;
	int b = 3;
	//-a = b;
	
	complex com1 (10,20);
	com1.myshow();
	(-com1).myshow();
	(-(-com1)).myshow();
	complex com2(1,2);
	//-com1 = com2;
	
}

要注意,const类的成员函数可以访问类的所有成员变量(包括const和非const),但不能修改他们(包括非const成员变量)。const类对象只能调用const成员函数

重载流运算符

重载<<

 

#include <iostream>
using namespace std;

class mytype
{
	public:
		mytype(int x = 0, int y = 0):x(x),y(y){}
		friend ostream& operator<<(ostream &os,const mytype &t2);
	private:
		int x;
		int y;
};

ostream& operator<<(ostream &os,const mytype &t2)
{
	os << '(' << t2.x << ',' << t2.y << ')';
	return os;
}

int main()
{
	mytype t1;
	//cin >> t1;
	//cout << t1 << endl;	想要实现这些功能,要重载<<和>>,但是重载为成员函数是不现实的,因为无法修改cout的库 
	mytype t2(10,20);
	cout << t1 << endl << t2;
	return 0;
}

ostream& operator<<(ostream &os,const mytype &t2)解读:cout的类型是ostream,因为<<操作符通常用于链式调用,例如std::cout << obj1 << obj2;。为了支持这种用法,operator<<必须返回它操作的流对象的引用,这样后续的<<操作可以继续在同一个流对象上执行。同时cout只是进行展示,不改变t2里面的内容,所以要加上const

重载>>

#include <iostream>
using namespace std;

class mytype
{
	public:
		mytype(int x = 0, int y = 0):x(x),y(y){}
		friend ostream& operator<<(ostream &is,const mytype &t2);
		friend istream& operator>>(istream &is,mytype &t);
	private:
		int x;
		int y;
};

ostream& operator<<(ostream &os,const mytype &t)
{
	os << '(' << t.x << ',' << t.y << ')';
	return os;
}

istream& operator>>(istream &is,mytype &t)
{
	is >> t.x >> t.y;
	return is;
}

int main()
{
	mytype t1;
	//cin >> t1;
	//cout << t1 << endl;	想要实现这些功能,要重载<<和>>,但是重载为成员函数是不现实的,因为无法修改cout的库 
	mytype t2(10,20);
	cin >> t1 >> t2;
	cout << t1 << endl << t2;
	return 0;
}

注意:输入流会改变mytype对象里面的数字,所以不能用const修饰

cin和cout在操作时都会对应改变输入流和输出的状态所以都不能用const修饰

输入输出流运算符重载模板

istream &operator>>(istream &is,mytype &a);
ostream &operator<<(ostream &os,mytype &a);

不可重载的运算符

. 成员访问运算符

.* 成员指针访问运算符

:: 域运算符

sizeof 长度运算符

?: 条件运算符

只能重载为成员函数的运算符

= 赋值运算符

[] 下标运算符

() 函数运算符

-> 间接成员访问运算符

->* 间接取值访问运算符

注意:

  1. 一个操作符左右两侧可能不是同类的对象,这时候要清楚这个操作符重载函数声明为谁的友元谁的成员

  2. 一般声明为左操作数对应的类的成员,因为是左操作数调用了这个函数

  3. 一般声明为右操作数对应的类的友元,因为是访问了右操作数的参数

#include <iostream>
using namespace std;

class sset;
class sender
{
	public:
		sender(string x = ""):name(x){}
		sender& operator<<(const sset &s);
	private:
		string name;
};

class sset
{
	public:
		sset(int a = 0,string x = "",int b = 0):id(a),nannv(x),saleray(b){}
		friend sender& sender::operator<<(const sset &s);
	private:
		int id;
		string nannv;
		int saleray;
};

sender& sender::operator<<(const sset &s)
{
	cout << s.id << endl;
	cout << s.nannv << endl;
	cout << s.saleray << endl;
	return *this;
}

int main()
{
	sender sen1("bob");
	sset sset1(18,"man",23000);
	sen1 << sset1;
	return 0;
}

 

这里要注意和重载cout后面的<<的不同,虽然cout << mytype中<<左右两侧也是不同的类对象(左边是ostream类,右边是mytype类),但是operator<<充当的是全局函数,并未让cout对其进行调用而是将cout传进去了,本质是os<<(operator<<(os,mytype1),mytype2),而在下面这里我们用了两个类里面私有对象,我们必须变为其成员或者友元,所以必须将这个函数设为前面那个类的成员,后面那个类的友元

类型转换构造函数(单参数的构造器,实现将其他类转换为本类的构造函数)

#include <iostream>
using namespace std;

class point2d
{
	public:
		point2d(int x = 0,int y = 0):_x(x),_y(y){}
		void show()
		{
			cout << '(' << _x << ',' << _y << ')' << endl;
		}
		friend class point3d;//构造函数不是独立的函数,而是类的一部分,并且它们不能单独被声明为友元
	private:
		int _x;
		int _y;
};
class point3d
{
	public:
		point3d(int x = 0,int y = 0,int z = 0):_x(x),_y(y),_z(z){}
		void show()
		{
			cout << '(' << _x << ',' << _y << ',' << _z << ')' << endl;
		}
		point3d(const point2d& p2)
		{
			this->_x = p2._x;
			this->_y = p2._y;
			this->_z = 999;
		}
	private:
		int _x;
		int _y;
		int _z;
};
int main()
{
	point2d p21(5,10); 
	point3d p31;
	p31 = p21;
	p21.show();
	p31.show();
	return 0;
}

注意:

  1. 这个构造参数要放在转化成的那个类里面;

  2. 构造函数不是独立的函数,而是类的一部分,并且它们不能单独被声明为友元,所以不能写friend point3d::point3d(const point2d& p2);

  3. 不需要先行声明是因为friend class point3d就是一个先行声明

  4. 如果有多个参数,那就只能叫做构造函数而不是转换函数

explicit关键字

以显示的方式完成转化,static_cast<目标类>(源类对象),否则就会报错

explicit point3d(const point2d& p2)
		{
			this->_x = p2._x;
			this->_y = p2._y;
			this->_z = 999;
		}

下面就不能再写p31 = p21了,因为这是隐式转化

而要写成p31 = (point3d)p21或者p31 = static_cast < point3d > (p21)了,这两个一个是C风格的显式转化,一个是C++风格的显式转化

类型转换运算符

与之前的类型转化构造函数不同,将b类变为a类,类型转化构造函数是将函数写在目标类也就是a类里面,类型转换运算符是写在源类也就是b类里面

operator 目标类型(void)
{
    return 目标类型(this->x,this->y);
}
#include <iostream>
using namespace std;

class point3d;
class point2d
{
	public:
		point2d(int x = 0,int y = 0):_x(x),_y(y){}
		void show()
		{
			cout << '(' << _x << ',' << _y << ')' << endl;
		}
		operator point3d(void); 
	private:
		int _x;
		int _y;
};

class point3d
{
	public:
		point3d(int x = 0,int y = 0,int z = 0):_x(x),_y(y),_z(z){}
		void show()
		{
			cout << '(' << _x << ',' << _y << ',' << _z << ')' << endl;
		}
	private:
		int _x;
		int _y;
		int _z;
};

point2d::operator point3d(void)
{
	return point3d(this->_x,this->_y,999);
}

int main()
{
	point2d p21(5,10); 
	point3d p31;
	p31 = p21;
	p21.show();
	p31.show();
	return 0;
}

注意:

  1. 这种写法要注意声明和实现的前后位置,要注意先行声明point3d

  2. 同样要注意operator point3d(void)都是函数名,这个函数无返回类型无参数列表,但是还要返回一个point3d类的对象

  3. point2d::operator point3d(void)是在 point2d 类中定义了一个类型转换运算符,它允许 point2d 类的对象隐式地转换为 point3d 类的对象,而不是重载了point3d的构造参数

重载()

重载()可以将类作为函数使用,即仿函数

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;


//bool cmp(int i,int j)
//{
//	return i > j;
//}

class Cmp
{
	public:
		bool operator()(int i,int j)
		{
			return i > j;
		}
};

int main()
{
	vector<int>vec = {23,45,87,2,63,8,4,62,18,96,343};
	//sort(vec.begin(),vec.end(),cmp);
	sort(vec.begin(),vec.end(),Cmp());
	for(int i = 0;i < vec.size(); ++i)
	{
		cout << vec[i] << ' ';
	}
	return 0;
} 

这里使用的就是Cmp()这个仿函数作为sort的比较函数

new、delete和malloc、free之间的区别

new会拉起对应类的构造函数,malloc不会

delete会拉起对应类的析构函数,free不会

C98下的智能指针auto_prt

#include <iostream>
#include <memory>
using namespace std;


class A
{
	public:
		A()
		{
			cout << "A()" << endl;
		}
		~A()
		{
			cout << "~A()" << endl;
		}
		void show()
		{
			cout << "void show()" << endl;
		}
	private:
		int num; 	
};

void func()
{
//	A *p = new A;
//	delete p;
//很多时候只记得构造而忘了析构会导致内存泄漏 

	auto_ptr<A> ptr (new A); //这里的ptr是一个对象,所以初始化是 ptr (new A)而不是ptr = new A
	ptr->show(); 
	(*ptr).show();
}

int main()
{
	func(); 
	//A *pt = new A;只会调用一次构造函数而没有析构 
	//A a; 都会调用,当出了main的栈就超出了a的作用域,就会自动调用析构函数 
	return 0;
}

自实现智能指针的相关功能

#include <iostream>
#include <memory>
using namespace std;


class A
{
	public:
		A()
		{
			cout << "A()" << endl;
		}
		~A()
		{
			cout << "~A()" << endl;
		}
		void show()
		{
			cout << "void show()" << endl;
		}
	private:
		int num; 	
};

class smt
{
	public:
		smt(A*_ptr = nullptr):ptr(_ptr){}
		~smt()
		{
			delete ptr; 
		}
		A*& operator->()
		{
			return ptr; 
		}
		A& operator*()
		{
			return *ptr;
		}
	private:
		A* ptr; 
}; 

void func()
{
//	A *p = new A;
//	delete p;
//很多时候只记得构造而忘了析构会导致内存泄漏 

//	auto_ptr<A> ptr (new A); //这里的ptr是一个对象,所以初始化是 ptr (new A)而不是ptr = new A
//	ptr->show(); 
//	(*ptr).show();

	smt smt1(new A);
	smt1->show();
	(*smt1).show();
}

int main()
{
	func(); 
	//A *pt = new A;只会调用一次构造函数而没有析构 
	//A a; 都会调用,当出了main的栈就超出了a的作用域,就会自动调用析构函数 
	return 0;
}

smt1->show();等价于(smt1.operator->())->show();

(*smt1).show();等价于(smt1.operator *()).show();

继承和派生

class son_name : public father_name

对于继承方式,简单来说,抛开子类里面自己特有的成员不说,公有继承就是将父类的共有和保护变成自己的共有和保护,保护继承就是将父类的共有和保护都变成自己的保护,私有继承就是将父类的共有和保护都变成自己的私有,但是,不管如何父类的私有成员都无法显示,都不具有访问权限

对于子类而言,不论何种继承,都会将父类除了构造器和析构器全盘接收,只是对于父类的私有成员无法显示(是不可见的),不具有访问权限(除非通过父类中的某些函数)

由于子类没有继承父类的构造器所以需要自己书写

#include <iostream>
using namespace std;

class father
{
	public:
		father(string _name = "",string _nannv = "",int _age = 0)
		:name(_name),nannv(_nannv),age(_age){}
		void f_show()
		{
			cout << "name  :" << name << endl;
			cout << "nannv :" << nannv << endl;
			cout << "age   :" << age << endl;
		}
		
	private:
		string name;
		string nannv;
		int age;
};

class son : public father
{
	public:
		son(string _name = "",string _nannv = "",int _age = 0,string _school = "")
		:father(_name,_nannv,_age),school(_school){}
		void s_show()
		{
			f_show();
			cout << "school:" << school << endl;
		}
	private:
		string school;
};

int main()
{
	father f("bob","男",41);
	son s("dick","男",20,"山西大学");
	f.f_show();
	cout << "---------------------------" << endl;
	s.s_show();
	return 0;
}

 

注意:

  1. 子类要包含父类的所有数据,但是无法直接访问父类中的private成员,所以可以借助父类的构造器构造子类构造器中的属于父类的private类型的成员,除此以外还要包含自己的成员

  2. 想要直接调用父类中的private成员是非法的,因为父类中的private成员在子类中是不可见更不可访问的,所以只能通过父类中的对应函数进行访问(如果有的话)

  3. 子类构造函数的固定格式为

子类名(总参数列表以及默认参数):父类名(父类参数列表),子类特有的参数{}

对于子类的构造器,父类名(父类参数列表)这部分必须在子类构造器的初始化列表中,子类特有对象可以在函数体中进行初始化

子类名(总参数列表以及默认参数):父类名(父类参数列表)
{
    子类特有参数 = 具体参数;

 对于子类的构造器,当父类构造器中包含默认构造器时,就可以不显式地调用父类构造器

#include <iostream>
using namespace std;

class father
{
	public:
		father(int a = 0):a(a){}
		void show()
		{
			cout << "father" << endl;
		}
	private:
		int a;
};

class son:public father
{
	public:
		son(){}
		son(int x):x(x){}
		void show()
		{
			father::show();
			cout << "x = " << x << endl;
		}
	private:
		int x;
};

int main()
{
	father f;
	son s1;
	son s2(888);
	f.show();
	s1.show();
	s2.show();
	return 0;
}

 这里father类里面包含了默认的无参构造器,子类的构造器就可以不显式地调用父类构造器,当然也无法给父类中的成员变量进行赋值,想要给父类中继承过来的成员变量进行赋值我们就要在子类构造器中显式地调用父类带参构造器

#include <iostream>
using namespace std;

class father
{
	public:
		father(int a):a(a){}
		void show()
		{
			cout << "father" << endl;
		}
	private:
		int a;
};

class son:public father
{
	public:
		son(int a):father(a)
		//就想有子类的无参构造器但是父类没有无参构造器怎么办
		//可以使用下面两种办法,直接用数字给父类赋值即可 
		//{}son():father(23){}
		//{}son(x = 0):father(23),x(x){}
		son(int x,int a):father(a),x(x){}
		void show()
		{
			father::show();
			cout << "x = " << x << endl;
		}
	private:
		int x;
};//这里的son没有无参构造

int main()
{
	father f(10);
	son s1(100,5);
	son s2(888,7);
	f.show();
	s1.show();
	s2.show();
	return 0;
}

注意:父类没有无参构造器,子类中一定包含着父类,所以在调用子类构造器的时候一定会调用父类构造器,而父类构造器必须进行显式调用

总结:当父类构造器中包含默认构造器时,并且不需要给父类进行赋值时,就可以不显式地调用父类构造器